CengJing
7 years ago
74 changed files with 16720 additions and 2146 deletions
-
25.github/ISSUE_TEMPLATE.md
-
9.github/PULL_REQUEST_TEMPLATE.md
-
47.travis.yml
-
63Dockerfile
-
537README.md
-
6166acme.sh
-
257deploy/README.md
-
26deploy/apache.sh
-
64deploy/cpanel_uapi.sh
-
26deploy/dovecot.sh
-
114deploy/exim4.sh
-
108deploy/fritzbox.sh
-
26deploy/haproxy.sh
-
31deploy/keychain.sh
-
77deploy/kong.sh
-
28deploy/myapi.sh
-
26deploy/mysqld.sh
-
26deploy/nginx.sh
-
26deploy/opensshd.sh
-
26deploy/pureftpd.sh
-
205deploy/ssh.sh
-
55deploy/strongswan.sh
-
100deploy/unifi.sh
-
56deploy/vault_cli.sh
-
110deploy/vsftpd.sh
-
788dnsapi/README.md
-
171dnsapi/dns-cf.sh
-
234dnsapi/dns-cx.sh
-
229dnsapi/dns-dp.sh
-
61dnsapi/dns-myapi.sh
-
147dnsapi/dns_ad.sh
-
202dnsapi/dns_ali.sh
-
264dnsapi/dns_autodns.sh
-
340dnsapi/dns_aws.sh
-
339dnsapi/dns_azure.sh
-
199dnsapi/dns_cf.sh
-
184dnsapi/dns_cloudns.sh
-
181dnsapi/dns_cx.sh
-
328dnsapi/dns_cyon.sh
-
184dnsapi/dns_da.sh
-
205dnsapi/dns_dgon.sh
-
215dnsapi/dns_dnsimple.sh
-
148dnsapi/dns_do.sh
-
161dnsapi/dns_dp.sh
-
97dnsapi/dns_dreamhost.sh
-
128dnsapi/dns_duckdns.sh
-
339dnsapi/dns_dyn.sh
-
228dnsapi/dns_dynu.sh
-
326dnsapi/dns_freedns.sh
-
123dnsapi/dns_gandi_livedns.sh
-
178dnsapi/dns_gd.sh
-
158dnsapi/dns_he.sh
-
102dnsapi/dns_infoblox.sh
-
311dnsapi/dns_inwx.sh
-
176dnsapi/dns_ispconfig.sh
-
95dnsapi/dns_knot.sh
-
78dnsapi/dns_lexicon.sh
-
183dnsapi/dns_linode.sh
-
154dnsapi/dns_lua.sh
-
157dnsapi/dns_me.sh
-
35dnsapi/dns_myapi.sh
-
166dnsapi/dns_namecom.sh
-
137dnsapi/dns_namesilo.sh
-
158dnsapi/dns_nsone.sh
-
58dnsapi/dns_nsupdate.sh
-
318dnsapi/dns_ovh.sh
-
184dnsapi/dns_pdns.sh
-
161dnsapi/dns_selectel.sh
-
170dnsapi/dns_servercow.sh
-
202dnsapi/dns_unoeuro.sh
-
149dnsapi/dns_vscale.sh
-
106dnsapi/dns_yandex.sh
-
85dnsapi/dns_zonomi.sh
-
1288le.sh
@ -0,0 +1,25 @@ |
|||
<!-- |
|||
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试输出,我帮不了你. |
|||
如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh |
|||
|
|||
If it is a bug report: |
|||
- make sure you are able to repro it on the latest released version. |
|||
You can install the latest version by: `acme.sh --upgrade` |
|||
|
|||
- Search the existing issues. |
|||
- Refer to the [WIKI](https://wiki.acme.sh). |
|||
- Debug info [Debug](https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh). |
|||
|
|||
--> |
|||
|
|||
Steps to reproduce |
|||
------------------ |
|||
|
|||
Debug log |
|||
----------------- |
|||
|
|||
``` |
|||
acme.sh --issue ..... --debug 2 |
|||
``` |
|||
|
|||
|
@ -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. |
|||
|
|||
--> |
@ -0,0 +1,47 @@ |
|||
language: shell |
|||
sudo: required |
|||
dist: trusty |
|||
|
|||
os: |
|||
- linux |
|||
- osx |
|||
|
|||
services: |
|||
- docker |
|||
|
|||
env: |
|||
global: |
|||
- SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64 |
|||
|
|||
addons: |
|||
apt: |
|||
sources: |
|||
- debian-sid # Grab shellcheck from the Debian repo (o_O) |
|||
packages: |
|||
- shellcheck |
|||
|
|||
install: |
|||
- if [ "$TRAVIS_OS_NAME" = 'osx' ]; then |
|||
brew update && brew install socat; |
|||
export PATH="/usr/local/opt/openssl@1.1/bin:$PATH" ; |
|||
fi |
|||
|
|||
script: |
|||
- echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" |
|||
- command -V openssl && openssl version |
|||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi |
|||
- 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 -e SC2181 **/*.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 TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi |
|||
- if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi |
|||
|
|||
|
|||
matrix: |
|||
fast_finish: true |
|||
|
|||
|
@ -0,0 +1,63 @@ |
|||
FROM alpine:3.6 |
|||
|
|||
RUN apk update -f \ |
|||
&& apk --no-cache add -f \ |
|||
openssl \ |
|||
curl \ |
|||
socat \ |
|||
&& rm -rf /var/cache/apk/* |
|||
|
|||
ENV LE_CONFIG_HOME /acme.sh |
|||
|
|||
ENV AUTO_UPGRADE 1 |
|||
|
|||
#Install |
|||
ADD ./ /install_acme.sh/ |
|||
RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/ |
|||
|
|||
|
|||
RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab - |
|||
|
|||
RUN for verb in help \ |
|||
version \ |
|||
install \ |
|||
uninstall \ |
|||
upgrade \ |
|||
issue \ |
|||
signcsr \ |
|||
deploy \ |
|||
install-cert \ |
|||
renew \ |
|||
renew-all \ |
|||
revoke \ |
|||
remove \ |
|||
list \ |
|||
showcsr \ |
|||
install-cronjob \ |
|||
uninstall-cronjob \ |
|||
cron \ |
|||
toPkcs \ |
|||
toPkcs8 \ |
|||
update-account \ |
|||
register-account \ |
|||
create-account-key \ |
|||
create-domain-key \ |
|||
createCSR \ |
|||
deactivate \ |
|||
deactivate-account \ |
|||
; do \ |
|||
printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ |
|||
; done |
|||
|
|||
RUN printf "%b" '#!'"/usr/bin/env sh\n \ |
|||
if [ \"\$1\" = \"daemon\" ]; then \n \ |
|||
trap \"echo stop && killall crond && exit 0\" SIGTERM SIGINT \n \ |
|||
crond && while true; do sleep 1; done;\n \ |
|||
else \n \ |
|||
exec -- \"\$@\"\n \ |
|||
fi" >/entry.sh && chmod +x /entry.sh |
|||
|
|||
VOLUME /acme.sh |
|||
|
|||
ENTRYPOINT ["/entry.sh"] |
|||
CMD ["--help"] |
@ -1,229 +1,508 @@ |
|||
# le: means simp`Le` |
|||
Simplest shell script for LetsEncrypt free Certificate client |
|||
# An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh) |
|||
|
|||
[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) |
|||
- An ACME protocol client written purely in Shell (Unix shell) language. |
|||
- Full ACME protocol implementation. |
|||
- Support ACME v1 and ACME v2 |
|||
- Support ACME v2 wildcard certs |
|||
- Simple, powerful and very easy to use. You only need 3 minutes to learn it. |
|||
- Bash, dash and sh compatible. |
|||
- Simplest shell script for Let's Encrypt free certificate 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. |
|||
- DOES NOT require `root/sudoer` access. |
|||
- Docker friendly |
|||
- IPv6 support |
|||
|
|||
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. |
|||
|
|||
Wiki: https://github.com/Neilpang/acme.sh/wiki |
|||
|
|||
For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker) |
|||
|
|||
Twitter: [@neilpangxa](https://twitter.com/neilpangxa) |
|||
|
|||
|
|||
# [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) |
|||
|
|||
# Who: |
|||
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) |
|||
- [ruby-china.org](https://ruby-china.org/topics/31983) |
|||
- [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) |
|||
- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) |
|||
- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) |
|||
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) |
|||
- [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) |
|||
- [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html) |
|||
- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) |
|||
- [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) |
|||
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) |
|||
- [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) |
|||
|
|||
Simple and Powerful, you only need 3 minutes to learn. |
|||
# Tested OS |
|||
|
|||
Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client. |
|||
Just one script, to issue, renew your certificates automatically. |
|||
| NO | Status| Platform| |
|||
|----|-------|---------| |
|||
|1|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu |
|||
|2|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/debian-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian |
|||
|3|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/centos-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS |
|||
|4|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/windows-cygwin.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) |
|||
|5|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/freebsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD |
|||
|6|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/pfsense.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense |
|||
|7|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/opensuse-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE |
|||
|8|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/alpine-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl) |
|||
|9|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/base-archlinux.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux |
|||
|10|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/fedora-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora |
|||
|11|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/kalilinux-kali-linux-docker.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux |
|||
|12|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/oraclelinux-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux |
|||
|13|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/proxmox.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh |
|||
|14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111 |
|||
|15|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/openbsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD |
|||
|16|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/mageia.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia |
|||
|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) |
|||
|18|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris |
|||
|19|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux |
|||
|20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX |
|||
|
|||
For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest): |
|||
|
|||
https://github.com/Neilpang/acmetest |
|||
|
|||
|
|||
# Supported modes |
|||
|
|||
- Webroot mode |
|||
- Standalone mode |
|||
- Apache mode |
|||
- Nginx mode |
|||
- DNS mode |
|||
- [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode) |
|||
- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) |
|||
|
|||
|
|||
# 1. How to install |
|||
|
|||
### 1. Install online |
|||
|
|||
Check this project: https://github.com/Neilpang/get.acme.sh |
|||
|
|||
```bash |
|||
curl https://get.acme.sh | sh |
|||
``` |
|||
|
|||
Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt. |
|||
Or: |
|||
|
|||
Do NOT require to be `root/sudoer`. |
|||
```bash |
|||
wget -O - https://get.acme.sh | sh |
|||
``` |
|||
|
|||
#Tested OS |
|||
1. Ubuntu/Debian. |
|||
2. CentOS |
|||
3. Windows (cygwin with curl, openssl and crontab included) |
|||
4. FreeBSD with bash |
|||
|
|||
### 2. Or, Install from git |
|||
|
|||
#Supported Mode |
|||
1. Webroot mode |
|||
2. Standalone mode |
|||
3. Apache mode |
|||
4. Dns mode |
|||
Clone this project and launch installation: |
|||
|
|||
#How to use |
|||
```bash |
|||
git clone https://github.com/Neilpang/acme.sh.git |
|||
cd ./acme.sh |
|||
./acme.sh --install |
|||
``` |
|||
|
|||
1. Clone this project: https://github.com/Neilpang/le.git |
|||
You `don't have to be root` then, although `it is recommended`. |
|||
|
|||
2. Install le: |
|||
``` |
|||
./le.sh install |
|||
Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install |
|||
|
|||
The installer will perform 3 actions: |
|||
|
|||
1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. |
|||
All certs will be placed in this folder too. |
|||
2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. |
|||
3. Create daily cron job to check and renew the certs if needed. |
|||
|
|||
Cron entry example: |
|||
|
|||
```bash |
|||
0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null |
|||
``` |
|||
You don't have to be root then, although it is recommended. |
|||
|
|||
Which does 3 jobs: |
|||
* create and copy `le.sh` to your home dir: `~/.le` |
|||
All the certs will be placed in this folder. |
|||
* create alias : `le.sh=~/.le/le.sh` and `le=~/.le/le.sh`. |
|||
* create everyday cron job to check and renew the cert if needed. |
|||
After the installation, you must close the current terminal and reopen it to make the alias take effect. |
|||
|
|||
After install, you must close current terminal and reopen again 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: |
|||
|
|||
```sh |
|||
root@v1:~# acme.sh -h |
|||
``` |
|||
root@v1:~# le.sh |
|||
https://github.com/Neilpang/le |
|||
v1.1.1 |
|||
Usage: le.sh [command] ...[args].... |
|||
Available commands: |
|||
|
|||
install: |
|||
Install le.sh to your system. |
|||
issue: |
|||
Issue a cert. |
|||
installcert: |
|||
Install the issued cert to apache/nginx or any other server. |
|||
renew: |
|||
Renew a cert. |
|||
renewAll: |
|||
Renew all the certs. |
|||
uninstall: |
|||
Uninstall le.sh, and uninstall the cron job. |
|||
version: |
|||
Show version info. |
|||
installcronjob: |
|||
Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. |
|||
uninstallcronjob: |
|||
Uninstall the cron job. The 'uninstall' command can do this automatically. |
|||
createAccountKey: |
|||
Create an account private key, professional use. |
|||
createDomainKey: |
|||
Create an domain private key, professional use. |
|||
createCSR: |
|||
Create CSR , professional use. |
|||
# 2. Just issue a cert |
|||
|
|||
**Example 1:** Single domain. |
|||
|
|||
```bash |
|||
acme.sh --issue -d example.com -w /home/wwwroot/example.com |
|||
``` |
|||
|
|||
or: |
|||
|
|||
root@v1:~/le# le issue |
|||
Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no |
|||
```bash |
|||
acme.sh --issue -d example.com -w /home/username/public_html |
|||
``` |
|||
|
|||
or: |
|||
|
|||
```bash |
|||
acme.sh --issue -d example.com -w /var/www/html |
|||
``` |
|||
|
|||
Set the param value to "no" means you want to ignore it. |
|||
**Example 2:** Multiple domains in the same cert. |
|||
|
|||
For example, if you give "no" to "key-length", it will use default length 2048. |
|||
```bash |
|||
acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com |
|||
``` |
|||
|
|||
And if you give 'no' to 'cert-file-path', it will not copy the issued cert to the "cert-file-path". |
|||
The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder. |
|||
|
|||
In all the cases, the issued cert will be placed in "~/.le/domain.com/" |
|||
Second argument **"example.com"** is the main domain you want to issue the cert for. |
|||
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`. |
|||
|
|||
# Just issue a cert: |
|||
``` |
|||
le issue /home/wwwroot/aa.com aa.com www.aa.com,cp.aa.com |
|||
``` |
|||
First argument `/home/wwwroot/aa.com` is the web root folder, You must have `write` access to this folder. |
|||
The certs will be placed in `~/.acme.sh/example.com/` |
|||
|
|||
Second argument "aa.com" is the main domain you want to issue cert for. |
|||
The certs will be renewed automatically every **60** days. |
|||
|
|||
Third argument is the additional domain list you want to use. Comma separated list, which is Optional. |
|||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert |
|||
|
|||
You must point and bind all the domains to the same webroot dir:`/home/wwwroot/aa.com` |
|||
|
|||
The cert will be placed in `~/.le/aa.com/` |
|||
# 3. Install the cert to Apache/Nginx etc. |
|||
|
|||
The issued cert will be renewed every 80 days automatically. |
|||
After the cert is generated, 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 the future. |
|||
|
|||
# Install issued cert to apache/nginx etc. |
|||
**Apache** example: |
|||
```bash |
|||
acme.sh --install-cert -d example.com \ |
|||
--cert-file /path/to/certfile/in/apache/cert.pem \ |
|||
--key-file /path/to/keyfile/in/apache/key.pem \ |
|||
--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ |
|||
--reloadcmd "service apache2 force-reload" |
|||
``` |
|||
le installcert aa.com /path/to/certfile/in/apache/nginx /path/to/keyfile/in/apache/nginx /path/to/ca/certfile/apache/nginx "service apache2|nginx reload" |
|||
|
|||
**Nginx** example: |
|||
```bash |
|||
acme.sh --install-cert -d example.com \ |
|||
--key-file /path/to/keyfile/in/nginx/key.pem \ |
|||
--fullchain-file /path/to/fullchain/nginx/cert.pem \ |
|||
--reloadcmd "service nginx force-reload" |
|||
``` |
|||
|
|||
Install the issued cert/key to the production apache or nginx path. |
|||
Only the domain is required, all the other parameters are optional. |
|||
|
|||
The cert will be renewed every 80 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` |
|||
The ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission. |
|||
|
|||
Install/copy the cert/key to the production Apache or Nginx path. |
|||
|
|||
# Use Standalone server to issue cert( requires you be root/sudoer, or you have permission to listen tcp 80 port): |
|||
Same usage as all above, just give `no` as the webroot. |
|||
The tcp `80` port must be free to listen, otherwise you will be prompted to free the `80` port and try again. |
|||
The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. |
|||
|
|||
``` |
|||
le issue no aa.com www.aa.com,cp.aa.com |
|||
``` |
|||
|
|||
# Use Apache mode(requires you 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. |
|||
Particularly, if you are running an apache server, you can use apache mode instead. Which doesn't write any file to your web root folder. |
|||
**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** |
|||
|
|||
Just set string "apache" to the first argument, it will use apache plugin automatically. |
|||
# 4. Use Standalone server to issue cert |
|||
|
|||
**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** |
|||
|
|||
Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. |
|||
|
|||
```bash |
|||
acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com |
|||
``` |
|||
le issue apache aa.com www.aa.com,user.aa.com |
|||
|
|||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert |
|||
|
|||
|
|||
# 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. |
|||
|
|||
Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. |
|||
|
|||
```bash |
|||
acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com |
|||
``` |
|||
All the other arguments are the same with previous. |
|||
|
|||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert |
|||
|
|||
# Use DNS mode: |
|||
Support the latest dns-01 challenge. |
|||
|
|||
# 6. Use Apache mode |
|||
|
|||
**(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`. |
|||
|
|||
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. |
|||
|
|||
Just set string "apache" as the second argument and it will force use of apache plugin automatically. |
|||
|
|||
```sh |
|||
acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com |
|||
``` |
|||
le issue dns aa.com www.aa.com,user.aa.com |
|||
|
|||
**This apache mode is only to issue the cert, it will not change your apache config files. |
|||
You will need to configure your website config files to use the cert by yourself. |
|||
We don't want to mess your apache server, don't worry.** |
|||
|
|||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert |
|||
|
|||
# 7. Use Nginx mode |
|||
|
|||
**(requires you to be root/sudoer, since it is required to interact with Nginx server)** |
|||
|
|||
If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. |
|||
|
|||
Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. |
|||
|
|||
Just set string "nginx" as the second argument. |
|||
|
|||
It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. |
|||
|
|||
So, the config is not changed. |
|||
|
|||
```sh |
|||
acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com |
|||
``` |
|||
|
|||
You will get the output like bellow: |
|||
**This nginx mode is only to issue the cert, it will not change your nginx config files. |
|||
You will need to configure your website config files to use the cert by yourself. |
|||
We don't want to mess your nginx server, don't worry.** |
|||
|
|||
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert |
|||
|
|||
# 8. Automatic DNS API integration |
|||
|
|||
If your DNS provider supports API access, we can use that API to automatically issue the certs. |
|||
|
|||
You don't have to do anything manually! |
|||
|
|||
### Currently acme.sh supports: |
|||
|
|||
1. CloudFlare.com API |
|||
1. DNSPod.cn API |
|||
1. CloudXNS.com API |
|||
1. GoDaddy.com API |
|||
1. PowerDNS.com API |
|||
1. OVH, kimsufi, soyoustart and runabove API |
|||
1. nsupdate API |
|||
1. LuaDNS.com API |
|||
1. DNSMadeEasy.com API |
|||
1. AWS Route 53 |
|||
1. aliyun.com(阿里云) API |
|||
1. ISPConfig 3.1 API |
|||
1. Alwaysdata.com API |
|||
1. Linode.com API |
|||
1. FreeDNS (https://freedns.afraid.org/) |
|||
1. cyon.ch |
|||
1. Domain-Offensive/Resellerinterface/Domainrobot API |
|||
1. Gandi LiveDNS API |
|||
1. Knot DNS API |
|||
1. DigitalOcean API (native) |
|||
1. ClouDNS.net API |
|||
1. Infoblox NIOS API (https://www.infoblox.com/) |
|||
1. VSCALE (https://vscale.io/) |
|||
1. Dynu API (https://www.dynu.com) |
|||
1. DNSimple API |
|||
1. NS1.com API |
|||
1. DuckDNS.org API |
|||
1. Name.com API |
|||
1. Dyn Managed DNS API |
|||
1. Yandex PDD API (https://pdd.yandex.ru) |
|||
1. Hurricane Electric DNS service (https://dns.he.net) |
|||
1. UnoEuro API (https://www.unoeuro.com/) |
|||
1. INWX (https://www.inwx.de/) |
|||
1. Servercow (https://servercow.de) |
|||
1. Namesilo (https://www.namesilo.com) |
|||
1. InternetX autoDNS API (https://internetx.com) |
|||
1. Azure DNS |
|||
1. selectel.com(selectel.ru) DNS API |
|||
1. zonomi.com DNS API |
|||
1. DreamHost.com API |
|||
1. DirectAdmin API |
|||
|
|||
|
|||
And: |
|||
|
|||
**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.)** |
|||
|
|||
|
|||
**More APIs coming soon...** |
|||
|
|||
If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project. |
|||
|
|||
For more details: [How to use DNS API](dnsapi) |
|||
|
|||
# 9. Use DNS manual mode: |
|||
|
|||
If your dns provider doesn't support any api access, you can add the txt record by your hand. |
|||
|
|||
```bash |
|||
acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com |
|||
``` |
|||
|
|||
You should get an output like below: |
|||
|
|||
```sh |
|||
Add the following txt record: |
|||
Domain:_acme-challenge.aa.com |
|||
Domain:_acme-challenge.example.com |
|||
Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c |
|||
|
|||
Add the following txt record: |
|||
Domain:_acme-challenge.www.aa.com |
|||
Domain:_acme-challenge.www.example.com |
|||
Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
|||
``` |
|||
|
|||
Please add those txt records to the domains. Waiting for the dns to take effect. |
|||
``` |
|||
|
|||
Then just rerun with `renew` argument: |
|||
|
|||
```bash |
|||
acme.sh --renew -d example.com |
|||
``` |
|||
|
|||
Ok, it's done. |
|||
|
|||
**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** |
|||
|
|||
**Please use dns api mode instead.** |
|||
|
|||
# 10. Issue ECC certificates |
|||
|
|||
`Let's Encrypt` can now issue **ECDSA** certificates. |
|||
|
|||
And we support them too! |
|||
|
|||
Just set the `keylength` parameter with a prefix `ec-`. |
|||
|
|||
Then just retry with 'renew' command: |
|||
For example: |
|||
|
|||
### Single domain ECC certificate |
|||
|
|||
```bash |
|||
acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 |
|||
``` |
|||
le renew aa.com |
|||
|
|||
### SAN multi domain ECC certificate |
|||
|
|||
```bash |
|||
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 |
|||
``` |
|||
|
|||
Ok, it's finished. |
|||
Please look at the `keylength` parameter above. |
|||
|
|||
Valid values are: |
|||
|
|||
1. **ec-256 (prime256v1, "ECDSA P-256")** |
|||
2. **ec-384 (secp384r1, "ECDSA P-384")** |
|||
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** |
|||
|
|||
|
|||
#Automatic dns api integeration |
|||
|
|||
If your dns provider supports api access, we can use api to automatically issue certs. |
|||
You don't have do anything manually. |
|||
# 11. Issue Wildcard certificates |
|||
|
|||
###Currently we support: |
|||
It's simple, just give a wildcard domain as the `-d` parameter. |
|||
|
|||
1. Cloudflare.com api |
|||
2. Dnspod.cn api |
|||
3. Cloudxns.com api |
|||
```sh |
|||
acme.sh --issue -d example.com -d *.example.com --dns dns_cf |
|||
``` |
|||
|
|||
More apis are comming soon.... |
|||
|
|||
If your dns provider is not in the supported list above, you can write your own script api easily. |
|||
|
|||
For more details: [How to use dns api](dnsapi) |
|||
# 12. How to renew the certs |
|||
|
|||
No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. |
|||
|
|||
# Issue ECC certificate: |
|||
LetsEncrypt now can issue ECDSA certificate. |
|||
And we also support it. |
|||
However, you can also force to renew a cert: |
|||
|
|||
Just set the `length` parameter with a prefix `ec-`. |
|||
For example: |
|||
```sh |
|||
acme.sh --renew -d example.com --force |
|||
``` |
|||
le issue /home/wwwroot/aa.com aa.com www.aa.com ec-256 |
|||
|
|||
or, for ECC cert: |
|||
|
|||
```sh |
|||
acme.sh --renew -d example.com --force --ecc |
|||
``` |
|||
Please look at the last parameter above. |
|||
|
|||
Valid values are: |
|||
|
|||
1. ec-256 (prime256v1, "ECDSA P-256") |
|||
2. ec-384 (secp384r1, "ECDSA P-384") |
|||
3. ec-521 (secp521r1, "ECDSA P-521", which is not supported by letsencrypt yet.) |
|||
# 13. How to stop cert renewal |
|||
|
|||
To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: |
|||
|
|||
```sh |
|||
acme.sh --remove -d example.com [--ecc] |
|||
``` |
|||
|
|||
The cert/key file is not removed from the disk. |
|||
|
|||
You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. |
|||
|
|||
|
|||
# 14. How to upgrade `acme.sh` |
|||
|
|||
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: |
|||
|
|||
```sh |
|||
acme.sh --upgrade |
|||
``` |
|||
|
|||
You can also enable auto upgrade: |
|||
|
|||
```sh |
|||
acme.sh --upgrade --auto-upgrade |
|||
``` |
|||
|
|||
#Under the Hood |
|||
Then **acme.sh** will be kept up to date automatically. |
|||
|
|||
Speak ACME language with bash directly to Let's encrypt. |
|||
Disable auto upgrade: |
|||
|
|||
```sh |
|||
acme.sh --upgrade --auto-upgrade 0 |
|||
``` |
|||
|
|||
|
|||
# 15. Issue a cert from an existing CSR |
|||
|
|||
https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR |
|||
|
|||
|
|||
# 16. Under the Hood |
|||
|
|||
Speak ACME language using shell, directly to "Let's Encrypt". |
|||
|
|||
TODO: |
|||
|
|||
|
|||
#Acknowledgment |
|||
# 17. Acknowledgments |
|||
|
|||
1. Acme-tiny: https://github.com/diafygi/acme-tiny |
|||
2. ACME protocol: https://github.com/ietf-wg-acme/acme |
|||
3. letsencrypt: https://github.com/letsencrypt/letsencrypt |
|||
|
|||
|
|||
|
|||
#License & Other |
|||
# 18. License & Others |
|||
|
|||
License is GPLv3 |
|||
|
|||
Please Star and Fork me. |
|||
|
|||
Issues and pull requests are welcomed. |
|||
[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. |
|||
|
|||
|
|||
# 19. Donate |
|||
Your donation makes **acme.sh** better: |
|||
|
|||
1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) |
|||
|
|||
[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) |
6166
acme.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,257 @@ |
|||
# Using deploy api |
|||
|
|||
Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). |
|||
|
|||
Here are the scripts to deploy the certs/key to the server/services. |
|||
|
|||
## 1. Deploy the certs to your cpanel host |
|||
|
|||
If you want to deploy using cpanel UAPI see 7. |
|||
|
|||
(cpanel deploy hook is not finished yet, this is just an example.) |
|||
|
|||
|
|||
|
|||
Then you can deploy now: |
|||
|
|||
```sh |
|||
export DEPLOY_CPANEL_USER=myusername |
|||
export DEPLOY_CPANEL_PASSWORD=PASSWORD |
|||
acme.sh --deploy -d example.com --deploy-hook cpanel |
|||
``` |
|||
|
|||
## 2. Deploy ssl cert on kong proxy engine based on api |
|||
|
|||
Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). |
|||
Currently supports Kong-v0.10.x. |
|||
|
|||
```sh |
|||
acme.sh --deploy -d ftp.example.com --deploy-hook kong |
|||
``` |
|||
|
|||
## 3. Deploy the cert to remote server through SSH access |
|||
|
|||
The ssh deploy plugin allows you to deploy certificates to a remote host |
|||
using SSH command to connect to the remote server. The ssh plugin is invoked |
|||
with the following command... |
|||
|
|||
```sh |
|||
acme.sh --deploy -d example.com --deploy-hook ssh |
|||
``` |
|||
Prior to running this for the first time you must tell the plugin where |
|||
and how to deploy the certificates. This is done by exporting the following |
|||
environment variables. This is not required for subsequent runs as the |
|||
values are stored by acme.sh in the domain configuration files. |
|||
|
|||
Required... |
|||
``` |
|||
export DEPLOY_SSH_USER=username |
|||
``` |
|||
Optional... |
|||
``` |
|||
export DEPLOY_SSH_CMD=custom ssh command |
|||
export DEPLOY_SSH_SERVER=url or ip address of remote host |
|||
export DEPLOY_SSH_KEYFILE=filename for private key |
|||
export DEPLOY_SSH_CERTFILE=filename for certificate file |
|||
export DEPLOY_SSH_CAFILE=filename for intermediate CA file |
|||
export DEPLOY_SSH_FULLCHAIN=filename for fullchain file |
|||
export DEPLOY_SSH_REMOTE_CMD=command to execute on remote host |
|||
export DEPLOY_SSH_BACKUP=yes or no |
|||
``` |
|||
|
|||
**DEPLOY_SSH_USER** |
|||
Username at the remote host that SSH will login with. Note that |
|||
SSH must be able to login to remote host without a password... SSH Keys |
|||
must have been exchanged with the remote host. Validate and test that you |
|||
can login to USER@URL from the host running acme.sh before using this script. |
|||
|
|||
The USER@URL at the remote server must also have has permissions to write to |
|||
the target location of the certificate files and to execute any commands |
|||
(e.g. to stop/start services). |
|||
|
|||
**DEPLOY_SSH_CMD** |
|||
You can customize the ssh command used to connect to the remote host. For example |
|||
if you need to connect to a specific port at the remote server you can set this |
|||
to, for example, "ssh -p 22" or to use `sshpass` to provide password inline |
|||
instead of exchanging ssh keys (this is not recommended, using keys is |
|||
more secure). |
|||
|
|||
**DEPLOY_SSH_SERVER** |
|||
URL or IP Address of the remote server. If not provided then the domain |
|||
name provided on the acme.sh --deploy command line is used. |
|||
|
|||
**DEPLOY_SSH_KEYFILE** |
|||
Target filename for the private key issued by LetsEncrypt. |
|||
|
|||
**DEPLOY_SSH_CERTFILE** |
|||
Target filename for the certificate issued by LetsEncrypt. |
|||
If this is the same as the previous filename (for keyfile) then it is |
|||
appended to the same file. |
|||
|
|||
**DEPLOY_SSH_CAFILE** |
|||
Target filename for the CA intermediate certificate issued by LetsEncrypt. |
|||
If this is the same as a previous filename (for keyfile or certfile) then |
|||
it is appended to the same file. |
|||
|
|||
**DEPLOY_SSH_FULLCHAIN** |
|||
Target filename for the fullchain certificate issued by LetsEncrypt. |
|||
If this is the same as a previous filename (for keyfile, certfile or |
|||
cafile) then it is appended to the same file. |
|||
|
|||
**DEPLOY_SSH_REMOTE_CMD** |
|||
Command to execute on the remote server after copying any certificates. This |
|||
could be any additional command required for example to stop and restart |
|||
the service. |
|||
|
|||
**DEPLOY_SSH_BACKUP** |
|||
Before writing a certificate file to the remote server the existing |
|||
certificate will be copied to a backup directory on the remote server. |
|||
These are placed in a hidden directory in the home directory of the SSH |
|||
user |
|||
```sh |
|||
~/.acme_ssh_deploy/[domain name]-backup-[timestamp] |
|||
``` |
|||
Any backups older than 180 days will be deleted when new certificates |
|||
are deployed. This defaults to "yes" set to "no" to disable backup. |
|||
|
|||
###Examples using SSH deploy |
|||
The following example illustrates deploying certificates to a QNAP NAS |
|||
(tested with QTS version 4.2.3) |
|||
|
|||
```sh |
|||
export DEPLOY_SSH_USER="admin" |
|||
export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" |
|||
export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" |
|||
export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" |
|||
export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart" |
|||
|
|||
acme.sh --deploy -d qnap.example.com --deploy-hook ssh |
|||
``` |
|||
Note how in this example both the private key and certificate point to |
|||
the same file. This will result in the certificate being appended |
|||
to the same file as the private key... a common requirement of several |
|||
services. |
|||
|
|||
The next example illustrates deploying certificates to a Unifi |
|||
Controller (tested with version 5.4.11). |
|||
|
|||
```sh |
|||
export DEPLOY_SSH_USER="root" |
|||
export DEPLOY_SSH_KEYFILE="/var/lib/unifi/unifi.example.com.key" |
|||
export DEPLOY_SSH_FULLCHAIN="/var/lib/unifi/unifi.example.com.cer" |
|||
export DEPLOY_SSH_REMOTE_CMD="openssl pkcs12 -export \ |
|||
-inkey /var/lib/unifi/unifi.example.com.key \ |
|||
-in /var/lib/unifi/unifi.example.com.cer \ |
|||
-out /var/lib/unifi/unifi.example.com.p12 \ |
|||
-name ubnt -password pass:temppass \ |
|||
&& keytool -importkeystore -deststorepass aircontrolenterprise \ |
|||
-destkeypass aircontrolenterprise \ |
|||
-destkeystore /var/lib/unifi/keystore \ |
|||
-srckeystore /var/lib/unifi/unifi.example.com.p12 \ |
|||
-srcstoretype PKCS12 -srcstorepass temppass -alias ubnt -noprompt \ |
|||
&& service unifi restart" |
|||
|
|||
acme.sh --deploy -d unifi.example.com --deploy-hook ssh |
|||
``` |
|||
In this example we execute several commands on the remote host |
|||
after the certificate files have been copied... to generate a pkcs12 file |
|||
compatible with Unifi, to import it into the Unifi keystore and then finally |
|||
to restart the service. |
|||
|
|||
Note also that once the certificate is imported |
|||
into the keystore the individual certificate files are no longer |
|||
required. We could if we desired delete those files immediately. If we |
|||
do that then we should disable backup at the remote host (as there are |
|||
no files to backup -- they were erased during deployment). For example... |
|||
```sh |
|||
export DEPLOY_SSH_BACKUP=no |
|||
# modify the end of the remote command... |
|||
&& rm /var/lib/unifi/unifi.example.com.key \ |
|||
/var/lib/unifi/unifi.example.com.cer \ |
|||
/var/lib/unifi/unifi.example.com.p12 \ |
|||
&& service unifi restart |
|||
``` |
|||
|
|||
## 4. Deploy the cert to local vsftpd server |
|||
|
|||
```sh |
|||
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd |
|||
``` |
|||
|
|||
The default vsftpd conf file is `/etc/vsftpd.conf`, if your vsftpd conf is not in the default location, you can specify one: |
|||
|
|||
```sh |
|||
export DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" |
|||
|
|||
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd |
|||
``` |
|||
|
|||
The default command to restart vsftpd server is `service vsftpd restart`, if it doesn't work, you can specify one: |
|||
|
|||
```sh |
|||
export DEPLOY_VSFTPD_RELOAD="/etc/init.d/vsftpd restart" |
|||
|
|||
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd |
|||
``` |
|||
|
|||
## 5. Deploy the cert to local exim4 server |
|||
|
|||
```sh |
|||
acme.sh --deploy -d ftp.example.com --deploy-hook exim4 |
|||
``` |
|||
|
|||
The default exim4 conf file is `/etc/exim/exim.conf`, if your exim4 conf is not in the default location, you can specify one: |
|||
|
|||
```sh |
|||
export DEPLOY_EXIM4_CONF="/etc/exim4/exim4.conf.template" |
|||
|
|||
acme.sh --deploy -d ftp.example.com --deploy-hook exim4 |
|||
``` |
|||
|
|||
The default command to restart exim4 server is `service exim4 restart`, if it doesn't work, you can specify one: |
|||
|
|||
```sh |
|||
export DEPLOY_EXIM4_RELOAD="/etc/init.d/exim4 restart" |
|||
|
|||
acme.sh --deploy -d ftp.example.com --deploy-hook exim4 |
|||
``` |
|||
|
|||
## 6. Deploy the cert to OSX Keychain |
|||
|
|||
```sh |
|||
acme.sh --deploy -d ftp.example.com --deploy-hook keychain |
|||
``` |
|||
|
|||
## 7. Deploy to cpanel host using UAPI |
|||
|
|||
This hook is using UAPI and works in cPanel & WHM version 56 or newer. |
|||
``` |
|||
acme.sh --deploy -d example.com --deploy-hook cpanel_uapi |
|||
``` |
|||
DEPLOY_CPANEL_USER is required only if you run the script as root and it should contain cpanel username. |
|||
```sh |
|||
export DEPLOY_CPANEL_USER=username |
|||
acme.sh --deploy -d example.com --deploy-hook cpanel_uapi |
|||
``` |
|||
Please note, that the cpanel_uapi hook will deploy only the first domain when your certificate will automatically renew. Therefore you should issue a separate certificate for each domain. |
|||
|
|||
## 8. Deploy the cert to your FRITZ!Box router |
|||
|
|||
You must specify the credentials that have administrative privileges on the FRITZ!Box in order to deploy the certificate, plus the URL of your FRITZ!Box, through the following environment variables: |
|||
```sh |
|||
$ export DEPLOY_FRITZBOX_USERNAME=my_username |
|||
$ export DEPLOY_FRITZBOX_PASSWORD=the_password |
|||
$ export DEPLOY_FRITZBOX_URL=https://fritzbox.example.com |
|||
``` |
|||
|
|||
After the first deployment, these values will be stored in your $HOME/.acme.sh/account.conf. You may now deploy the certificate like this: |
|||
|
|||
```sh |
|||
acme.sh --deploy -d fritzbox.example.com --deploy-hook fritzbox |
|||
``` |
|||
|
|||
## 9. Deploy the cert to strongswan |
|||
|
|||
```sh |
|||
acme.sh --deploy -d ftp.example.com --deploy-hook strongswan |
|||
``` |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to apache server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
apache_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "Deploy cert to apache server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,64 @@ |
|||
#!/usr/bin/env sh |
|||
# Here is the script to deploy the cert to your cpanel using the cpanel API. |
|||
# Uses command line uapi. --user option is needed only if run as root. |
|||
# Returns 0 when success. |
|||
# Written by Santeri Kannisto <santeri.kannisto@2globalnomads.info> |
|||
# Public domain, 2017 |
|||
|
|||
#export DEPLOY_CPANEL_USER=myusername |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
|
|||
cpanel_uapi_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
if ! _exists uapi; then |
|||
_err "The command uapi is not found." |
|||
return 1 |
|||
fi |
|||
if ! _exists php; then |
|||
_err "The command php is not found." |
|||
return 1 |
|||
fi |
|||
# read cert and key files and urlencode both |
|||
_certstr=$(cat "$_ccert") |
|||
_keystr=$(cat "$_ckey") |
|||
_cert=$(php -r "echo urlencode(\"$_certstr\");") |
|||
_key=$(php -r "echo urlencode(\"$_keystr\");") |
|||
|
|||
_debug _cert "$_cert" |
|||
_debug _key "$_key" |
|||
|
|||
if [ "$(id -u)" = 0 ]; then |
|||
if [ -z "$DEPLOY_CPANEL_USER" ]; then |
|||
_err "It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username" |
|||
return 1 |
|||
fi |
|||
_savedomainconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER" |
|||
_response=$(uapi --user="$DEPLOY_CPANEL_USER" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key") |
|||
else |
|||
_response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key") |
|||
fi |
|||
error_response="status: 0" |
|||
if test "${_response#*$error_response}" != "$_response"; then |
|||
_err "Error in deploying certificate:" |
|||
_err "$_response" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug response "$_response" |
|||
_info "Certificate successfully deployed" |
|||
return 0 |
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to dovecot server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
dovecot_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,114 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to exim4 server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
#DEPLOY_EXIM4_CONF="/etc/exim/exim.conf" |
|||
#DEPLOY_EXIM4_RELOAD="service exim4 restart" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
exim4_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_ssl_path="/etc/acme.sh/exim4" |
|||
if ! mkdir -p "$_ssl_path"; then |
|||
_err "Can not create folder:$_ssl_path" |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Copying key and cert" |
|||
_real_key="$_ssl_path/exim4.key" |
|||
if ! cat "$_ckey" >"$_real_key"; then |
|||
_err "Error: write key file to: $_real_key" |
|||
return 1 |
|||
fi |
|||
_real_fullchain="$_ssl_path/exim4.pem" |
|||
if ! cat "$_cfullchain" >"$_real_fullchain"; then |
|||
_err "Error: write key file to: $_real_fullchain" |
|||
return 1 |
|||
fi |
|||
|
|||
DEFAULT_EXIM4_RELOAD="service exim4 restart" |
|||
_reload="${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}" |
|||
|
|||
if [ -z "$IS_RENEW" ]; then |
|||
DEFAULT_EXIM4_CONF="/etc/exim/exim.conf" |
|||
if [ ! -f "$DEFAULT_EXIM4_CONF" ]; then |
|||
DEFAULT_EXIM4_CONF="/etc/exim4/exim4.conf.template" |
|||
fi |
|||
_exim4_conf="${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}" |
|||
_debug _exim4_conf "$_exim4_conf" |
|||
if [ ! -f "$_exim4_conf" ]; then |
|||
if [ -z "$DEPLOY_EXIM4_CONF" ]; then |
|||
_err "exim4 conf is not found, please define DEPLOY_EXIM4_CONF" |
|||
return 1 |
|||
else |
|||
_err "It seems that the specified exim4 conf is not valid, please check." |
|||
return 1 |
|||
fi |
|||
fi |
|||
if [ ! -w "$_exim4_conf" ]; then |
|||
_err "The file $_exim4_conf is not writable, please change the permission." |
|||
return 1 |
|||
fi |
|||
_backup_conf="$DOMAIN_BACKUP_PATH/exim4.conf.bak" |
|||
_info "Backup $_exim4_conf to $_backup_conf" |
|||
cp "$_exim4_conf" "$_backup_conf" |
|||
|
|||
_info "Modify exim4 conf: $_exim4_conf" |
|||
if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" \ |
|||
&& _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then |
|||
_info "Set config success!" |
|||
else |
|||
_err "Config exim4 server error, please report bug to us." |
|||
_info "Restoring exim4 conf" |
|||
if cat "$_backup_conf" >"$_exim4_conf"; then |
|||
_info "Restore conf success" |
|||
eval "$_reload" |
|||
else |
|||
_err "Oops, error restore exim4 conf, please report bug to us." |
|||
fi |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
_info "Run reload: $_reload" |
|||
if eval "$_reload"; then |
|||
_info "Reload success!" |
|||
if [ "$DEPLOY_EXIM4_CONF" ]; then |
|||
_savedomainconf DEPLOY_EXIM4_CONF "$DEPLOY_EXIM4_CONF" |
|||
else |
|||
_cleardomainconf DEPLOY_EXIM4_CONF |
|||
fi |
|||
if [ "$DEPLOY_EXIM4_RELOAD" ]; then |
|||
_savedomainconf DEPLOY_EXIM4_RELOAD "$DEPLOY_EXIM4_RELOAD" |
|||
else |
|||
_cleardomainconf DEPLOY_EXIM4_RELOAD |
|||
fi |
|||
return 0 |
|||
else |
|||
_err "Reload error, restoring" |
|||
if cat "$_backup_conf" >"$_exim4_conf"; then |
|||
_info "Restore conf success" |
|||
eval "$_reload" |
|||
else |
|||
_err "Oops, error restore exim4 conf, please report bug to us." |
|||
fi |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
|
|||
} |
@ -0,0 +1,108 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to an AVM FRITZ!Box router. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
#DEPLOY_FRITZBOX_USERNAME="username" |
|||
#DEPLOY_FRITZBOX_PASSWORD="password" |
|||
#DEPLOY_FRITZBOX_URL="https://fritz.box" |
|||
|
|||
# Kudos to wikrie at Github for his FRITZ!Box update script: |
|||
# https://gist.github.com/wikrie/f1d5747a714e0a34d0582981f7cb4cfb |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
fritzbox_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
if ! _exists iconv; then |
|||
_err "iconv not found" |
|||
return 1 |
|||
fi |
|||
|
|||
_fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}" |
|||
_fritzbox_password="${DEPLOY_FRITZBOX_PASSWORD}" |
|||
_fritzbox_url="${DEPLOY_FRITZBOX_URL}" |
|||
|
|||
_debug _fritzbox_url "$_fritzbox_url" |
|||
_debug _fritzbox_username "$_fritzbox_username" |
|||
_secure_debug _fritzbox_password "$_fritzbox_password" |
|||
if [ -z "$_fritzbox_username" ]; then |
|||
_err "FRITZ!Box username is not found, please define DEPLOY_FRITZBOX_USERNAME." |
|||
return 1 |
|||
fi |
|||
if [ -z "$_fritzbox_password" ]; then |
|||
_err "FRITZ!Box password is not found, please define DEPLOY_FRITZBOX_PASSWORD." |
|||
return 1 |
|||
fi |
|||
if [ -z "$_fritzbox_url" ]; then |
|||
_err "FRITZ!Box url is not found, please define DEPLOY_FRITZBOX_URL." |
|||
return 1 |
|||
fi |
|||
|
|||
_saveaccountconf DEPLOY_FRITZBOX_USERNAME "${_fritzbox_username}" |
|||
_saveaccountconf DEPLOY_FRITZBOX_PASSWORD "${_fritzbox_password}" |
|||
_saveaccountconf DEPLOY_FRITZBOX_URL "${_fritzbox_url}" |
|||
|
|||
# Do not check for a valid SSL certificate, because initially the cert is not valid, so it could not install the LE generated certificate |
|||
export HTTPS_INSECURE=1 |
|||
|
|||
_info "Log in to the FRITZ!Box" |
|||
_fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')" |
|||
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" |
|||
_fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')" |
|||
|
|||
if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then |
|||
_err "Logging in to the FRITZ!Box failed. Please check username, password and URL." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Generate form POST request" |
|||
_post_request="$(_mktemp)" |
|||
_post_boundary="---------------------------$(date +%Y%m%d%H%M%S)" |
|||
# _CERTPASSWORD_ is unset because Let's Encrypt certificates don't have a password. But if they ever do, here's the place to use it! |
|||
_CERTPASSWORD_= |
|||
{ |
|||
printf -- "--" |
|||
printf -- "%s\r\n" "${_post_boundary}" |
|||
printf "Content-Disposition: form-data; name=\"sid\"\r\n\r\n%s\r\n" "${_fritzbox_sid}" |
|||
printf -- "--" |
|||
printf -- "%s\r\n" "${_post_boundary}" |
|||
printf "Content-Disposition: form-data; name=\"BoxCertPassword\"\r\n\r\n%s\r\n" "${_CERTPASSWORD_}" |
|||
printf -- "--" |
|||
printf -- "%s\r\n" "${_post_boundary}" |
|||
printf "Content-Disposition: form-data; name=\"BoxCertImportFile\"; filename=\"BoxCert.pem\"\r\n" |
|||
printf "Content-Type: application/octet-stream\r\n\r\n" |
|||
cat "${_ckey}" "${_cfullchain}" |
|||
printf "\r\n" |
|||
printf -- "--" |
|||
printf -- "%s--" "${_post_boundary}" |
|||
} >>"${_post_request}" |
|||
|
|||
_info "Upload certificate to the FRITZ!Box" |
|||
|
|||
export _H1="Content-type: multipart/form-data boundary=${_post_boundary}" |
|||
_post "$(cat "${_post_request}")" "${_fritzbox_url}/cgi-bin/firmwarecfg" | grep SSL |
|||
|
|||
retval=$? |
|||
if [ $retval = 0 ]; then |
|||
_info "Upload successful" |
|||
else |
|||
_err "Upload failed" |
|||
fi |
|||
rm "${_post_request}" |
|||
|
|||
return $retval |
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to haproxy server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
haproxy_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "deploy cert to haproxy server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,31 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a sample custom api script. |
|||
#This file name is "myapi.sh" |
|||
#So, here must be a method myapi_deploy() |
|||
#Which will be called by acme.sh to deploy the cert |
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
keychain_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
/usr/bin/security import "$_ckey" -k "/Library/Keychains/System.keychain" |
|||
/usr/bin/security import "$_ccert" -k "/Library/Keychains/System.keychain" |
|||
/usr/bin/security import "$_cca" -k "/Library/Keychains/System.keychain" |
|||
/usr/bin/security import "$_cfullchain" -k "/Library/Keychains/System.keychain" |
|||
|
|||
return 0 |
|||
} |
@ -0,0 +1,77 @@ |
|||
#!/usr/bin/env sh |
|||
# If certificate already exist it will update only cert and key not touching other parameter |
|||
# If certificate doesn't exist it will only upload cert and key and not set other parameter |
|||
# Note that we deploy full chain |
|||
# 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 ssl_uuid linked to the domain |
|||
ssl_uuid=$(_get "$KONG_URL/certificates/$_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 "$ssl_uuid" ]; then |
|||
_debug "Unable to get Kong ssl_uuid for domain $_cdomain" |
|||
_debug "Make sure that KONG_URL is correctly configured" |
|||
_debug "Make sure that a Kong certificate match the sni" |
|||
_debug "Kong url: $KONG_URL" |
|||
_info "No existing certificate, creating..." |
|||
#return 1 |
|||
fi |
|||
#Save kong url if it's succesful (First run case) |
|||
_saveaccountconf KONG_URL "$KONG_URL" |
|||
#Generate DEIM |
|||
delim="-----MultipartDelimiter$(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) |
|||
if [ -z "$ssl_uuid" ]; then |
|||
#set sni to domain |
|||
content="--$delim${nl}Content-Disposition: form-data; name=\"snis\"${nl}${nl}$_cdomain" |
|||
fi |
|||
#add key |
|||
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"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=\"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 sslcreated (if not => POST else => PATCH) |
|||
|
|||
if [ -z "$ssl_uuid" ]; then |
|||
#Post certificate to Kong |
|||
response=$(_post "$content" "$KONG_URL/certificates" "" "POST") |
|||
else |
|||
#patch |
|||
response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH") |
|||
fi |
|||
if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then |
|||
_err "An error occurred with cert upload. Check response:" |
|||
_err "$response" |
|||
return 1 |
|||
fi |
|||
_debug response "$response" |
|||
_info "Certificate successfully deployed" |
|||
} |
@ -0,0 +1,28 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a sample custom api script. |
|||
#This file name is "myapi.sh" |
|||
#So, here must be a method myapi_deploy() |
|||
#Which will be called by acme.sh to deploy the cert |
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
myapi_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to mysqld server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
mysqld_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "deploy cert to mysqld server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to nginx server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
nginx_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "deploy cert to nginx server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to opensshd server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
opensshd_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "deploy cert to opensshd server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,26 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to pureftpd server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
pureftpd_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_err "deploy cert to pureftpd server, Not implemented yet" |
|||
return 1 |
|||
|
|||
} |
@ -0,0 +1,205 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Script to deploy certificates to remote server by SSH |
|||
# Note that SSH must be able to login to remote host without a password... |
|||
# SSH Keys must have been exchanged with the remote host. Validate and |
|||
# test that you can login to USER@SERVER from the host running acme.sh before |
|||
# using this script. |
|||
# |
|||
# The following variables exported from environment will be used. |
|||
# If not set then values previously saved in domain.conf file are used. |
|||
# |
|||
# Only a username is required. All others are optional. |
|||
# |
|||
# The following examples are for QNAP NAS running QTS 4.2 |
|||
# export DEPLOY_SSH_CMD="" # defaults to ssh |
|||
# export DEPLOY_SSH_USER="admin" # required |
|||
# export DEPLOY_SSH_SERVER="qnap" # defaults to domain name |
|||
# export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" |
|||
# export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" |
|||
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" |
|||
# export DEPLOY_SSH_FULLCHAIN="" |
|||
# export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart" |
|||
# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes |
|||
# |
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
ssh_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
_cmdstr="" |
|||
_homedir='~' |
|||
_backupprefix="$_homedir/.acme_ssh_deploy/$_cdomain-backup" |
|||
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')" |
|||
|
|||
if [ -f "$DOMAIN_CONF" ]; then |
|||
# shellcheck disable=SC1090 |
|||
. "$DOMAIN_CONF" |
|||
fi |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
# USER is required to login by SSH to remote host. |
|||
if [ -z "$DEPLOY_SSH_USER" ]; then |
|||
if [ -z "$Le_Deploy_ssh_user" ]; then |
|||
_err "DEPLOY_SSH_USER not defined." |
|||
return 1 |
|||
fi |
|||
else |
|||
Le_Deploy_ssh_user="$DEPLOY_SSH_USER" |
|||
_savedomainconf Le_Deploy_ssh_user "$Le_Deploy_ssh_user" |
|||
fi |
|||
|
|||
# SERVER is optional. If not provided then use _cdomain |
|||
if [ -n "$DEPLOY_SSH_SERVER" ]; then |
|||
Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER" |
|||
_savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server" |
|||
elif [ -z "$Le_Deploy_ssh_server" ]; then |
|||
Le_Deploy_ssh_server="$_cdomain" |
|||
fi |
|||
|
|||
# CMD is optional. If not provided then use ssh |
|||
if [ -n "$DEPLOY_SSH_CMD" ]; then |
|||
Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD" |
|||
_savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd" |
|||
elif [ -z "$Le_Deploy_ssh_cmd" ]; then |
|||
Le_Deploy_ssh_cmd="ssh" |
|||
fi |
|||
|
|||
# BACKUP is optional. If not provided then default to yes |
|||
if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then |
|||
Le_Deploy_ssh_backup="no" |
|||
elif [ -z "$Le_Deploy_ssh_backup" ]; then |
|||
Le_Deploy_ssh_backup="yes" |
|||
fi |
|||
_savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup" |
|||
|
|||
_info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server" |
|||
|
|||
# KEYFILE is optional. |
|||
# If provided then private key will be copied to provided filename. |
|||
if [ -n "$DEPLOY_SSH_KEYFILE" ]; then |
|||
Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE" |
|||
_savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile" |
|||
fi |
|||
if [ -n "$Le_Deploy_ssh_keyfile" ]; then |
|||
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then |
|||
# backup file we are about to overwrite. |
|||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_keyfile $_backupdir >/dev/null;" |
|||
fi |
|||
# copy new certificate into file. |
|||
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;" |
|||
_info "will copy private key to remote file $Le_Deploy_ssh_keyfile" |
|||
fi |
|||
|
|||
# CERTFILE is optional. |
|||
# If provided then private key will be copied or appended to provided filename. |
|||
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then |
|||
Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE" |
|||
_savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile" |
|||
fi |
|||
if [ -n "$Le_Deploy_ssh_certfile" ]; then |
|||
_pipe=">" |
|||
if [ "$Le_Deploy_ssh_certfile" = "$Le_Deploy_ssh_keyfile" ]; then |
|||
# if filename is same as previous file then append. |
|||
_pipe=">>" |
|||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then |
|||
# backup file we are about to overwrite. |
|||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_certfile $_backupdir >/dev/null;" |
|||
fi |
|||
# copy new certificate into file. |
|||
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;" |
|||
_info "will copy certificate to remote file $Le_Deploy_ssh_certfile" |
|||
fi |
|||
|
|||
# CAFILE is optional. |
|||
# If provided then CA intermediate certificate will be copied or appended to provided filename. |
|||
if [ -n "$DEPLOY_SSH_CAFILE" ]; then |
|||
Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE" |
|||
_savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile" |
|||
fi |
|||
if [ -n "$Le_Deploy_ssh_cafile" ]; then |
|||
_pipe=">" |
|||
if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] \ |
|||
|| [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_certfile" ]; then |
|||
# if filename is same as previous file then append. |
|||
_pipe=">>" |
|||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then |
|||
# backup file we are about to overwrite. |
|||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_cafile $_backupdir >/dev/null;" |
|||
fi |
|||
# copy new certificate into file. |
|||
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;" |
|||
_info "will copy CA file to remote file $Le_Deploy_ssh_cafile" |
|||
fi |
|||
|
|||
# FULLCHAIN is optional. |
|||
# If provided then fullchain certificate will be copied or appended to provided filename. |
|||
if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then |
|||
Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN" |
|||
_savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain" |
|||
fi |
|||
if [ -n "$Le_Deploy_ssh_fullchain" ]; then |
|||
_pipe=">" |
|||
if [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_keyfile" ] \ |
|||
|| [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_certfile" ] \ |
|||
|| [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_cafile" ]; then |
|||
# if filename is same as previous file then append. |
|||
_pipe=">>" |
|||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then |
|||
# backup file we are about to overwrite. |
|||
_cmdstr="$_cmdstr cp $Le_Deploy_ssh_fullchain $_backupdir >/dev/null;" |
|||
fi |
|||
# copy new certificate into file. |
|||
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;" |
|||
_info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain" |
|||
fi |
|||
|
|||
# REMOTE_CMD is optional. |
|||
# If provided then this command will be executed on remote host. |
|||
if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then |
|||
Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD" |
|||
_savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd" |
|||
fi |
|||
if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then |
|||
_cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;" |
|||
_info "Will execute remote command $Le_Deploy_ssh_remote_cmd" |
|||
fi |
|||
|
|||
if [ -z "$_cmdstr" ]; then |
|||
_err "No remote commands to excute. Failed to deploy certificates to remote server" |
|||
return 1 |
|||
elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then |
|||
# run cleanup on the backup directory, erase all older |
|||
# than 180 days (15552000 seconds). |
|||
_cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \ |
|||
do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \ |
|||
then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr" |
|||
# Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr" |
|||
# Create our backup directory for overwritten cert files. |
|||
_cmdstr="mkdir -p $_backupdir; $_cmdstr" |
|||
_info "Backup of old certificate files will be placed in remote directory $_backupdir" |
|||
_info "Backup directories erased after 180 days." |
|||
fi |
|||
|
|||
_debug "Remote commands to execute: $_cmdstr" |
|||
_info "Submitting sequence of commands to remote server by ssh" |
|||
# quotations in bash cmd below intended. Squash travis spellcheck error |
|||
# shellcheck disable=SC2029 |
|||
$Le_Deploy_ssh_cmd -T "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmdstr'" |
|||
_ret="$?" |
|||
|
|||
if [ "$_ret" != "0" ]; then |
|||
_err "Error code $_ret returned from $Le_Deploy_ssh_cmd" |
|||
fi |
|||
|
|||
return $_ret |
|||
} |
@ -0,0 +1,55 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a sample custom api script. |
|||
#This file name is "myapi.sh" |
|||
#So, here must be a method myapi_deploy() |
|||
#Which will be called by acme.sh to deploy the cert |
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
strongswan_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_info "Using strongswan" |
|||
|
|||
if [ -x /usr/sbin/ipsec ]; then |
|||
_ipsec=/usr/sbin/ipsec |
|||
elif [ -x /usr/sbin/strongswan ]; then |
|||
_ipsec=/usr/sbin/strongswan |
|||
elif [ -x /usr/local/sbin/ipsec ]; then |
|||
_ipsec=/usr/local/sbin/ipsec |
|||
else |
|||
_err "no strongswan or ipsec command is detected" |
|||
return 1 |
|||
fi |
|||
|
|||
_info _ipsec "$_ipsec" |
|||
|
|||
_confdir=$($_ipsec --confdir) |
|||
if [ $? -ne 0 ] || [ -z "$_confdir" ]; then |
|||
_err "no strongswan --confdir is detected" |
|||
return 1 |
|||
fi |
|||
|
|||
_info _confdir "$_confdir" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
cat "$_ckey" >"${_confdir}/ipsec.d/private/$(basename "$_ckey")" |
|||
cat "$_ccert" >"${_confdir}/ipsec.d/certs/$(basename "$_ccert")" |
|||
cat "$_cca" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cca")" |
|||
cat "$_cfullchain" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cfullchain")" |
|||
|
|||
$_ipsec reload |
|||
|
|||
} |
@ -0,0 +1,100 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to unifi server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
#DEPLOY_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore" |
|||
#DEPLOY_UNIFI_KEYPASS="aircontrolenterprise" |
|||
#DEPLOY_UNIFI_RELOAD="service unifi restart" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
unifi_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
if ! _exists keytool; then |
|||
_err "keytool not found" |
|||
return 1 |
|||
fi |
|||
|
|||
DEFAULT_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore" |
|||
_unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-$DEFAULT_UNIFI_KEYSTORE}" |
|||
DEFAULT_UNIFI_KEYPASS="aircontrolenterprise" |
|||
_unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-$DEFAULT_UNIFI_KEYPASS}" |
|||
DEFAULT_UNIFI_RELOAD="service unifi restart" |
|||
_reload="${DEPLOY_UNIFI_RELOAD:-$DEFAULT_UNIFI_RELOAD}" |
|||
|
|||
_debug _unifi_keystore "$_unifi_keystore" |
|||
if [ ! -f "$_unifi_keystore" ]; then |
|||
if [ -z "$DEPLOY_UNIFI_KEYSTORE" ]; then |
|||
_err "unifi keystore is not found, please define DEPLOY_UNIFI_KEYSTORE" |
|||
return 1 |
|||
else |
|||
_err "It seems that the specified unifi keystore is not valid, please check." |
|||
return 1 |
|||
fi |
|||
fi |
|||
if [ ! -w "$_unifi_keystore" ]; then |
|||
_err "The file $_unifi_keystore is not writable, please change the permission." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Generate import pkcs12" |
|||
_import_pkcs12="$(_mktemp)" |
|||
_toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root |
|||
if [ "$?" != "0" ]; then |
|||
_err "Oops, error creating import pkcs12, please report bug to us." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Modify unifi keystore: $_unifi_keystore" |
|||
if keytool -importkeystore \ |
|||
-deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \ |
|||
-srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \ |
|||
-alias unifi -noprompt; then |
|||
_info "Import keystore success!" |
|||
rm "$_import_pkcs12" |
|||
else |
|||
_err "Import unifi keystore error, please report bug to us." |
|||
rm "$_import_pkcs12" |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Run reload: $_reload" |
|||
if eval "$_reload"; then |
|||
_info "Reload success!" |
|||
if [ "$DEPLOY_UNIFI_KEYSTORE" ]; then |
|||
_savedomainconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE" |
|||
else |
|||
_cleardomainconf DEPLOY_UNIFI_KEYSTORE |
|||
fi |
|||
if [ "$DEPLOY_UNIFI_KEYPASS" ]; then |
|||
_savedomainconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS" |
|||
else |
|||
_cleardomainconf DEPLOY_UNIFI_KEYPASS |
|||
fi |
|||
if [ "$DEPLOY_UNIFI_RELOAD" ]; then |
|||
_savedomainconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD" |
|||
else |
|||
_cleardomainconf DEPLOY_UNIFI_RELOAD |
|||
fi |
|||
return 0 |
|||
else |
|||
_err "Reload error" |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
|
|||
} |
@ -0,0 +1,56 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Here is a script to deploy cert to hashicorp vault |
|||
# (https://www.vaultproject.io/) |
|||
# |
|||
# it requires the vault binary to be available in PATH, and the following |
|||
# environment variables: |
|||
# |
|||
# VAULT_PREFIX - this contains the prefix path in vault |
|||
# VAULT_ADDR - vault requires this to find your vault server |
|||
# |
|||
# additionally, you need to ensure that VAULT_TOKEN is avialable or |
|||
# `vault auth` has applied the appropriate authorization for the vault binary |
|||
# to access the vault server |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
vault_cli_deploy() { |
|||
|
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
# validate required env vars |
|||
if [ -z "$VAULT_PREFIX" ]; then |
|||
_err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$VAULT_ADDR" ]; then |
|||
_err "VAULT_ADDR needs to be defined (contains vault connection address)" |
|||
return 1 |
|||
fi |
|||
|
|||
VAULT_CMD=$(which vault) |
|||
if [ ! $? ]; then |
|||
_err "cannot find vault binary!" |
|||
return 1 |
|||
fi |
|||
|
|||
$VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 |
|||
$VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 |
|||
$VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 |
|||
|
|||
} |
@ -0,0 +1,110 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a script to deploy cert to vsftpd server. |
|||
|
|||
#returns 0 means success, otherwise error. |
|||
|
|||
#DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" |
|||
#DEPLOY_VSFTPD_RELOAD="service vsftpd restart" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#domain keyfile certfile cafile fullchain |
|||
vsftpd_deploy() { |
|||
_cdomain="$1" |
|||
_ckey="$2" |
|||
_ccert="$3" |
|||
_cca="$4" |
|||
_cfullchain="$5" |
|||
|
|||
_debug _cdomain "$_cdomain" |
|||
_debug _ckey "$_ckey" |
|||
_debug _ccert "$_ccert" |
|||
_debug _cca "$_cca" |
|||
_debug _cfullchain "$_cfullchain" |
|||
|
|||
_ssl_path="/etc/acme.sh/vsftpd" |
|||
if ! mkdir -p "$_ssl_path"; then |
|||
_err "Can not create folder:$_ssl_path" |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Copying key and cert" |
|||
_real_key="$_ssl_path/vsftpd.key" |
|||
if ! cat "$_ckey" >"$_real_key"; then |
|||
_err "Error: write key file to: $_real_key" |
|||
return 1 |
|||
fi |
|||
_real_fullchain="$_ssl_path/vsftpd.chain.pem" |
|||
if ! cat "$_cfullchain" >"$_real_fullchain"; then |
|||
_err "Error: write key file to: $_real_fullchain" |
|||
return 1 |
|||
fi |
|||
|
|||
DEFAULT_VSFTPD_RELOAD="service vsftpd restart" |
|||
_reload="${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}" |
|||
|
|||
if [ -z "$IS_RENEW" ]; then |
|||
DEFAULT_VSFTPD_CONF="/etc/vsftpd.conf" |
|||
_vsftpd_conf="${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}" |
|||
if [ ! -f "$_vsftpd_conf" ]; then |
|||
if [ -z "$DEPLOY_VSFTPD_CONF" ]; then |
|||
_err "vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF" |
|||
return 1 |
|||
else |
|||
_err "It seems that the specified vsftpd conf is not valid, please check." |
|||
return 1 |
|||
fi |
|||
fi |
|||
if [ ! -w "$_vsftpd_conf" ]; then |
|||
_err "The file $_vsftpd_conf is not writable, please change the permission." |
|||
return 1 |
|||
fi |
|||
_backup_conf="$DOMAIN_BACKUP_PATH/vsftpd.conf.bak" |
|||
_info "Backup $_vsftpd_conf to $_backup_conf" |
|||
cp "$_vsftpd_conf" "$_backup_conf" |
|||
|
|||
_info "Modify vsftpd conf: $_vsftpd_conf" |
|||
if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" \ |
|||
&& _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" \ |
|||
&& _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then |
|||
_info "Set config success!" |
|||
else |
|||
_err "Config vsftpd server error, please report bug to us." |
|||
_info "Restoring vsftpd conf" |
|||
if cat "$_backup_conf" >"$_vsftpd_conf"; then |
|||
_info "Restore conf success" |
|||
eval "$_reload" |
|||
else |
|||
_err "Oops, error restore vsftpd conf, please report bug to us." |
|||
fi |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
_info "Run reload: $_reload" |
|||
if eval "$_reload"; then |
|||
_info "Reload success!" |
|||
if [ "$DEPLOY_VSFTPD_CONF" ]; then |
|||
_savedomainconf DEPLOY_VSFTPD_CONF "$DEPLOY_VSFTPD_CONF" |
|||
else |
|||
_cleardomainconf DEPLOY_VSFTPD_CONF |
|||
fi |
|||
if [ "$DEPLOY_VSFTPD_RELOAD" ]; then |
|||
_savedomainconf DEPLOY_VSFTPD_RELOAD "$DEPLOY_VSFTPD_RELOAD" |
|||
else |
|||
_cleardomainconf DEPLOY_VSFTPD_RELOAD |
|||
fi |
|||
return 0 |
|||
else |
|||
_err "Reload error, restoring" |
|||
if cat "$_backup_conf" >"$_vsftpd_conf"; then |
|||
_info "Restore conf success" |
|||
eval "$_reload" |
|||
else |
|||
_err "Oops, error restore vsftpd conf, please report bug to us." |
|||
fi |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
} |
@ -1,86 +1,808 @@ |
|||
# 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 clourflare account to get your api key. |
|||
First you need to login to your CloudFlare account to get your API key. |
|||
|
|||
``` |
|||
export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
|
|||
export CF_Email="xxxx@sss.com" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
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` and will be reused when needed. |
|||
|
|||
|
|||
## 2. 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. |
|||
|
|||
Ok, let's issue cert now: |
|||
``` |
|||
le.sh issue dns-cf aa.com www.aa.com |
|||
export DP_Id="1234" |
|||
export DP_Key="sADDsdasdgdsf" |
|||
``` |
|||
|
|||
The `CF_Key` and `CF_Email` will be saved in `~/.le/account.conf`, when next time you use cloudflare api, it will reuse this key. |
|||
Ok, let's issue a cert now: |
|||
``` |
|||
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` and will be reused when needed. |
|||
|
|||
|
|||
## Use Dnspod.cn domain api to automatically issue cert |
|||
## 3. Use CloudXNS.com domain API to automatically issue cert |
|||
|
|||
For now, we support dnspod.cn integeration. |
|||
First you need to login to your CloudXNS account to get your API Key and Secret. |
|||
|
|||
First you need to login to your dnspod.cn account to get your api key and key id. |
|||
``` |
|||
export CX_Key="1234" |
|||
export CX_Secret="sADDsdasdgdsf" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_cx -d example.com -d www.example.com |
|||
``` |
|||
export DP_Id="1234" |
|||
|
|||
export DP_Key="sADDsdasdgdsf" |
|||
The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
|
|||
## 4. Use GoDaddy.com domain API to automatically issue cert |
|||
|
|||
First you need to login to your GoDaddy account to get your API Key and Secret. |
|||
|
|||
https://developer.godaddy.com/keys/ |
|||
|
|||
Please create a Production key, instead of a Test key. |
|||
|
|||
``` |
|||
export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd" |
|||
``` |
|||
|
|||
Ok, let's issue cert now: |
|||
Ok, let's issue a cert now: |
|||
``` |
|||
le.sh issue dns-dp aa.com www.aa.com |
|||
acme.sh --issue --dns dns_gd -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `DP_Id` and `DP_Key` will be saved in `~/.le/account.conf`, when next time you use dnspod.cn 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 Cloudxns.com domain api to automatically issue cert |
|||
## 5. Use PowerDNS embedded API to automatically issue cert |
|||
|
|||
For now, we support Cloudxns.com integeration. |
|||
First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration. |
|||
|
|||
First you need to login to your Cloudxns.com account to get your api key and key secret. |
|||
https://doc.powerdns.com/md/httpapi/README/ |
|||
|
|||
``` |
|||
export CX_Key="1234" |
|||
export PDNS_Url="http://ns.example.com:8081" |
|||
export PDNS_ServerId="localhost" |
|||
export PDNS_Token="0123456789ABCDEF" |
|||
export PDNS_Ttl=60 |
|||
``` |
|||
|
|||
export CX_Secret="sADDsdasdgdsf" |
|||
Ok, let's issue a cert now: |
|||
``` |
|||
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` and will be reused when needed. |
|||
|
|||
|
|||
## 6. Use OVH/kimsufi/soyoustart/runabove API to automatically issue cert |
|||
|
|||
https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api |
|||
|
|||
|
|||
## 7. Use nsupdate to automatically issue cert |
|||
|
|||
First, generate a key for updating the zone |
|||
``` |
|||
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} |
|||
``` |
|||
|
|||
Include this key in your named configuration |
|||
``` |
|||
include "/etc/named/keys/update.key"; |
|||
``` |
|||
|
|||
Next, configure your zone to allow dynamic updates. |
|||
|
|||
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.; |
|||
}; |
|||
} |
|||
``` |
|||
|
|||
Finally, make the DNS server and update Key available to `acme.sh` |
|||
|
|||
``` |
|||
export NSUPDATE_SERVER="dns.example.com" |
|||
export NSUPDATE_KEY="/path/to/your/nsupdate.key" |
|||
``` |
|||
|
|||
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 |
|||
|
|||
``` |
|||
export LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
export LUA_Email="xxxx@sss.com" |
|||
``` |
|||
|
|||
To issue a cert: |
|||
``` |
|||
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. |
|||
|
|||
|
|||
## 9. Use DNSMadeEasy domain API |
|||
|
|||
Get your API credentials at https://cp.dnsmadeeasy.com/account/info |
|||
|
|||
``` |
|||
export ME_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
export ME_Secret="qdfqsdfkjdskfj" |
|||
``` |
|||
|
|||
To issue a cert: |
|||
``` |
|||
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. |
|||
|
|||
|
|||
## 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 automatically |
|||
validate with acme.sh at FreeDNS. |
|||
|
|||
## 16. Use cyon.ch |
|||
|
|||
You only need to set your cyon.ch login credentials. |
|||
If you also have 2 Factor Authentication (OTP) enabled, you need to set your secret token too and have `oathtool` installed. |
|||
|
|||
``` |
|||
export CY_Username="your_cyon_username" |
|||
export CY_Password="your_cyon_password" |
|||
export CY_OTP_Secret="your_otp_secret" # Only required if using 2FA |
|||
``` |
|||
|
|||
To issue a cert: |
|||
``` |
|||
acme.sh --issue --dns dns_cyon -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API |
|||
|
|||
You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`: |
|||
``` |
|||
export DO_PID="KD-1234567" |
|||
export DO_PW="cdfkjl3n2" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_do -d example.com -d www.example.com |
|||
``` |
|||
|
|||
## 18. Use Gandi LiveDNS API |
|||
|
|||
You must enable the new Gandi LiveDNS API first and the create your api key, See: http://doc.livedns.gandi.net/ |
|||
|
|||
``` |
|||
export GANDI_LIVEDNS_KEY="fdmlfsdklmfdkmqsdfk" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_gandi_livedns -d example.com -d www.example.com |
|||
``` |
|||
|
|||
## 19. Use Knot (knsupdate) DNS API to automatically issue cert |
|||
|
|||
First, generate a TSIG key for updating the zone. |
|||
|
|||
``` |
|||
keymgr tsig generate -t acme_key hmac-sha512 > /etc/knot/acme.key |
|||
``` |
|||
|
|||
Include this key in your knot configuration file. |
|||
|
|||
``` |
|||
include: /etc/knot/acme.key |
|||
``` |
|||
|
|||
Next, configure your zone to allow dynamic updates. |
|||
|
|||
Dynamic updates for the zone are allowed via proper ACL rule with the `update` action. For in-depth instructions, please see [Knot DNS's documentation](https://www.knot-dns.cz/documentation/). |
|||
|
|||
``` |
|||
acl: |
|||
- id: acme_acl |
|||
address: 192.168.1.0/24 |
|||
key: acme_key |
|||
action: update |
|||
|
|||
zone: |
|||
- domain: example.com |
|||
file: example.com.zone |
|||
acl: acme_acl |
|||
``` |
|||
|
|||
Finally, make the DNS server and TSIG Key available to `acme.sh` |
|||
|
|||
``` |
|||
export KNOT_SERVER="dns.example.com" |
|||
export KNOT_KEY=`grep \# /etc/knot/acme.key | cut -d' ' -f2` |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_knot -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 20. Use DigitalOcean API (native) |
|||
|
|||
You need to obtain a read and write capable API key from your DigitalOcean account. See: https://www.digitalocean.com/help/api/ |
|||
|
|||
``` |
|||
export DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_dgon -d example.com -d www.example.com |
|||
``` |
|||
|
|||
## 21. Use ClouDNS.net API |
|||
|
|||
You need to set the HTTP API user ID and password credentials. See: https://www.cloudns.net/wiki/article/42/. For security reasons, it's recommended to use a sub user ID that only has access to the necessary zones, as a regular API user has access to your entire account. |
|||
|
|||
``` |
|||
# Use this for a sub auth ID |
|||
export CLOUDNS_SUB_AUTH_ID=XXXXX |
|||
# Use this for a regular auth ID |
|||
#export CLOUDNS_AUTH_ID=XXXXX |
|||
export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com |
|||
``` |
|||
The `CLOUDNS_AUTH_ID` and `CLOUDNS_AUTH_PASSWORD` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 22. Use Infoblox API |
|||
|
|||
First you need to create/obtain API credentials on your Infoblox appliance. |
|||
|
|||
``` |
|||
export Infoblox_Creds="username:password" |
|||
export Infoblox_Server="ip or fqdn of infoblox appliance" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_infoblox -d example.com -d www.example.com |
|||
``` |
|||
|
|||
Note: This script will automatically create and delete the ephemeral txt record. |
|||
The `Infoblox_Creds` and `Infoblox_Server` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
|
|||
## 23. Use VSCALE API |
|||
|
|||
First you need to create/obtain API tokens on your [settings panel](https://vscale.io/panel/settings/tokens/). |
|||
|
|||
``` |
|||
VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_vscale -d example.com -d www.example.com |
|||
``` |
|||
|
|||
## 24. Use Dynu API |
|||
|
|||
First you need to create/obtain API credentials from your Dynu account. See: https://www.dynu.com/resources/api/documentation |
|||
|
|||
``` |
|||
export Dynu_ClientId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" |
|||
export Dynu_Secret="yyyyyyyyyyyyyyyyyyyyyyyyy" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_dynu -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `Dynu_ClientId` and `Dynu_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 25. Use DNSimple API |
|||
|
|||
First you need to login to your DNSimple account and generate a new oauth token. |
|||
|
|||
https://dnsimple.com/a/{your account id}/account/access_tokens |
|||
|
|||
Note that this is an _account_ token and not a user token. The account token is |
|||
needed to infer the `account_id` used in requests. A user token will not be able |
|||
to determine the correct account to use. |
|||
|
|||
``` |
|||
export DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
``` |
|||
|
|||
To issue the cert just specify the `dns_dnsimple` API. |
|||
|
|||
Ok, let's issue cert now: |
|||
``` |
|||
le.sh issue dns-cx aa.com www.aa.com |
|||
acme.sh --issue --dns dns_dnsimple -d example.com |
|||
``` |
|||
|
|||
The `CX_Key` and `CX_Secret` will be saved in `~/.le/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. |
|||
The `DNSimple_OAUTH_TOKEN` will be saved in `~/.acme.sh/account.conf` and will |
|||
be reused when needed. |
|||
|
|||
If you have any issues with this integration please report them to |
|||
https://github.com/pho3nixf1re/acme.sh/issues. |
|||
|
|||
## 26. Use NS1.com API |
|||
|
|||
``` |
|||
export NS1_Key="fdmlfsdklmfdkmqsdfk" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_nsone -d example.com -d www.example.com |
|||
``` |
|||
|
|||
# Use custom api |
|||
## 27. Use DuckDNS.org API |
|||
|
|||
If your api is not supported yet, you can write your own dns api. |
|||
``` |
|||
export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" |
|||
``` |
|||
|
|||
Let's assume you want to name it 'myapi', |
|||
Please note that since DuckDNS uses StartSSL as their cert provider, thus |
|||
--insecure may need to be used when issuing certs: |
|||
``` |
|||
acme.sh --insecure --issue --dns dns_duckdns -d mydomain.duckdns.org |
|||
``` |
|||
|
|||
1. Create a bash script named `~/.le/dns-myapi.sh`, |
|||
2. In the scrypt, you must have a function named `dns-myapi-add()`. Which will be called by le.sh to add dns records. |
|||
3. Then you can use your api to issue cert like: |
|||
For issues, please report to https://github.com/raidenii/acme.sh/issues. |
|||
|
|||
## 28. Use Name.com API |
|||
|
|||
You'll need to fill out the form at https://www.name.com/reseller/apply to apply |
|||
for API username and token. |
|||
|
|||
``` |
|||
export Namecom_Username="testuser" |
|||
export Namecom_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
|||
``` |
|||
|
|||
And now you can issue certs with: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_namecom -d example.com -d www.example.com |
|||
``` |
|||
|
|||
For issues, please report to https://github.com/raidenii/acme.sh/issues. |
|||
|
|||
## 29. Use Dyn Managed DNS API to automatically issue cert |
|||
|
|||
First, login to your Dyn Managed DNS account: https://portal.dynect.net/login/ |
|||
|
|||
It is recommended to add a new user specific for API access. |
|||
|
|||
The minimum "Zones & Records Permissions" required are: |
|||
``` |
|||
RecordAdd |
|||
RecordUpdate |
|||
RecordDelete |
|||
RecordGet |
|||
ZoneGet |
|||
ZoneAddNode |
|||
ZoneRemoveNode |
|||
ZonePublish |
|||
``` |
|||
|
|||
Pass the API user credentials to the environment: |
|||
``` |
|||
export DYN_Customer="customer" |
|||
export DYN_Username="apiuser" |
|||
export DYN_Password="secret" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_dyn -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `DYN_Customer`, `DYN_Username` and `DYN_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 30. Use pdd.yandex.ru API |
|||
|
|||
``` |
|||
export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
|||
``` |
|||
|
|||
Follow these instructions to get the token for your domain https://tech.yandex.com/domain/doc/concepts/access-docpage/ |
|||
``` |
|||
acme.sh --issue --dns dns_yandex -d mydomain.example.org |
|||
``` |
|||
|
|||
For issues, please report to https://github.com/non7top/acme.sh/issues. |
|||
|
|||
## 31. Use Hurricane Electric |
|||
|
|||
Hurricane Electric (https://dns.he.net/) doesn't have an API so just set your login credentials like so: |
|||
|
|||
``` |
|||
export HE_Username="yourusername" |
|||
export HE_Password="password" |
|||
``` |
|||
|
|||
Then you can issue your certificate: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_he -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `HE_Username` and `HE_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
Please report any issues to https://github.com/angel333/acme.sh or to <me@ondrejsimek.com>. |
|||
|
|||
## 32. Use UnoEuro API to automatically issue cert |
|||
|
|||
First you need to login to your UnoEuro account to get your API key. |
|||
|
|||
``` |
|||
export UNO_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
export UNO_User="UExxxxxx" |
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
le.sh issue dns-myapi aa.com www.aa.com |
|||
acme.sh --issue --dns dns_unoeuro -d example.com -d www.example.com |
|||
``` |
|||
|
|||
For more details, please check our sample script: [dns-myapi.sh](dns-myapi.sh) |
|||
The `UNO_Key` and `UNO_User` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 33. Use INWX |
|||
|
|||
[INWX](https://www.inwx.de/) offers an [xmlrpc api](https://www.inwx.de/de/help/apidoc) with your standard login credentials, set them like so: |
|||
|
|||
``` |
|||
export INWX_User="yourusername" |
|||
export INWX_Password="password" |
|||
``` |
|||
|
|||
Then you can issue your certificates with: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_inwx -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `INWX_User` and `INWX_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 34. User Servercow API v1 |
|||
|
|||
Create a new user from the servercow control center. Don't forget to activate **DNS API** for this user. |
|||
|
|||
``` |
|||
export SERVERCOW_API_Username=username |
|||
export SERVERCOW_API_Password=password |
|||
``` |
|||
|
|||
Now you cann issue a cert: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_servercow -d example.com -d www.example.com |
|||
``` |
|||
Both, `SERVERCOW_API_Username` and `SERVERCOW_API_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 35. Use Namesilo.com API |
|||
|
|||
You'll need to generate an API key at https://www.namesilo.com/account_api.php |
|||
Optionally you may restrict the access to an IP range there. |
|||
|
|||
``` |
|||
export Namesilo_Key="xxxxxxxxxxxxxxxxxxxxxxxx" |
|||
``` |
|||
|
|||
And now you can issue certs with: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_namesilo --dnssleep 900 -d example.com -d www.example.com |
|||
``` |
|||
|
|||
## 36. Use autoDNS (InternetX) |
|||
|
|||
[InternetX](https://www.internetx.com/) offers an [xml api](https://help.internetx.com/display/API/AutoDNS+XML-API) with your standard login credentials, set them like so: |
|||
|
|||
``` |
|||
export AUTODNS_USER="yourusername" |
|||
export AUTODNS_PASSWORD="password" |
|||
export AUTODNS_CONTEXT="context" |
|||
``` |
|||
|
|||
Then you can issue your certificates with: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_autodns -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `AUTODNS_USER`, `AUTODNS_PASSWORD` and `AUTODNS_CONTEXT` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 37. Use Azure DNS |
|||
|
|||
You have to create a service principal first. See:[How to use Azure DNS](../../../wiki/How-to-use-Azure-DNS) |
|||
|
|||
``` |
|||
export AZUREDNS_SUBSCRIPTIONID="12345678-9abc-def0-1234-567890abcdef" |
|||
export AZUREDNS_TENANTID="11111111-2222-3333-4444-555555555555" |
|||
export AZUREDNS_APPID="3b5033b5-7a66-43a5-b3b9-a36b9e7c25ed" |
|||
export AZUREDNS_CLIENTSECRET="1b0224ef-34d4-5af9-110f-77f527d561bd" |
|||
``` |
|||
|
|||
Then you can issue your certificates with: |
|||
|
|||
``` |
|||
acme.sh --issue --dns dns_azure -d example.com -d www.example.com |
|||
``` |
|||
|
|||
`AZUREDNS_SUBSCRIPTIONID`, `AZUREDNS_TENANTID`,`AZUREDNS_APPID` and `AZUREDNS_CLIENTSECRET` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 38. Use selectel.com(selectel.ru) domain API to automatically issue cert |
|||
|
|||
First you need to login to your account to get your API key from: https://my.selectel.ru/profile/apikeys. |
|||
|
|||
```sh |
|||
export SL_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
|
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_selectel -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `SL_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 39. Use zonomi.com domain API to automatically issue cert |
|||
|
|||
First you need to login to your account to find your API key from: http://zonomi.com/app/dns/dyndns.jsp |
|||
|
|||
Your will find your api key in the example urls: |
|||
|
|||
```sh |
|||
https://zonomi.com/app/dns/dyndns.jsp?host=example.com&api_key=1063364558943540954358668888888888 |
|||
``` |
|||
|
|||
```sh |
|||
export ZM_Key="1063364558943540954358668888888888" |
|||
|
|||
``` |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_zonomi -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `ZM_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
## 40. Use DreamHost DNS API |
|||
|
|||
DNS API keys may be created at https://panel.dreamhost.com/?tree=home.api. |
|||
Ensure the created key has add and remove privelages. |
|||
|
|||
``` |
|||
export DH_API_Key="<api key>" |
|||
acme.sh --issue --dns dns_dreamhost -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The 'DH_API_KEY' will be saved in `~/.acme.sh/account.conf` and will |
|||
be reused when needed. |
|||
|
|||
## 41. Use DirectAdmin API |
|||
The DirectAdmin interface has it's own Let's encrypt functionality, but this |
|||
script can be used to generate certificates for names which are not hosted on |
|||
DirectAdmin |
|||
|
|||
User must provide login data and URL to the DirectAdmin incl. port. |
|||
You can create an user which only has access to |
|||
|
|||
- CMD_API_DNS_CONTROL |
|||
- CMD_API_SHOW_DOMAINS |
|||
|
|||
By using the Login Keys function. |
|||
See also https://www.directadmin.com/api.php and https://www.directadmin.com/features.php?id=1298 |
|||
|
|||
``` |
|||
export DA_Api="https://remoteUser:remotePassword@da.domain.tld:8443" |
|||
export DA_Api_Insecure=1 |
|||
``` |
|||
Set `DA_Api_Insecure` to 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) |
|||
|
|||
Ok, let's issue a cert now: |
|||
``` |
|||
acme.sh --issue --dns dns_da -d example.com -d www.example.com |
|||
``` |
|||
|
|||
The `DA_Api` and `DA_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. |
|||
|
|||
|
|||
# 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) |
|||
|
|||
See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide |
|||
|
|||
# Use lexicon DNS API |
|||
|
|||
https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api |
@ -1,171 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
|
|||
# |
|||
#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#CF_Email="xxxx@sss.com" |
|||
|
|||
|
|||
CF_Api="https://api.cloudflare.com/client/v4/" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns-cf-add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ] ; then |
|||
_err "You don't specify cloudflare api key and email yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf CF_Key "$CF_Key" |
|||
_saveaccountconf CF_Email "$CF_Email" |
|||
|
|||
_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" |
|||
|
|||
if [ "$?" != "0" ] || ! printf $response | grep \"success\":true > /dev/null ; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
count=$(printf $response | grep -o \"count\":[^,]* | cut -d : -f 2) |
|||
|
|||
if [ "$count" == "0" ] ; then |
|||
_info "Adding record" |
|||
if _cf_rest POST "/zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |
|||
if printf $response | grep $fulldomain > /dev/null ; then |
|||
_info "Added, sleeping 10 seconds" |
|||
sleep 10 |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
else |
|||
_info "Updating record" |
|||
record_id=$(printf $response | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") |
|||
_debug "record_id" $record_id |
|||
|
|||
_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 |
|||
_info "Updated, sleeping 10 seconds" |
|||
sleep 10 |
|||
#todo: check if the record takes effect |
|||
return 0; |
|||
fi |
|||
_err "Update error" |
|||
return 1 |
|||
fi |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
#################### Private functions bellow ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while [ '1' ] ; do |
|||
h=$(printf $domain | cut -d . -f $i-100) |
|||
if [ -z "$h" ] ; then |
|||
#not valid |
|||
return 1; |
|||
fi |
|||
|
|||
if ! _cf_rest GET "zones?name=$h" ; then |
|||
return 1 |
|||
fi |
|||
|
|||
if printf $response | grep \"name\":\"$h\" ; then |
|||
_domain_id=$(printf "$response" | grep -o \"id\":\"[^\"]*\" | head -1 | cut -d : -f 2 | tr -d \") |
|||
if [ "$_domain_id" ] ; then |
|||
_sub_domain=$(printf $domain | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
let "i+=1" |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
|
|||
_cf_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
_debug $ep |
|||
if [ "$3" ] ; then |
|||
data="$3" |
|||
_debug data "$data" |
|||
response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json" --data $data)" |
|||
else |
|||
response="$(curl --silent -X $m "$CF_Api/$ep" -H "X-Auth-Email: $CF_Email" -H "X-Auth-Key: $CF_Key" -H "Content-Type: application/json")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ] ; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug response "$response" |
|||
return 0 |
|||
} |
|||
|
|||
|
|||
_debug() { |
|||
|
|||
if [ -z "$DEBUG" ] ; then |
|||
return |
|||
fi |
|||
|
|||
if [ -z "$2" ] ; then |
|||
echo $1 |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_info() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_err() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" >&2 |
|||
else |
|||
echo "$1"="$2" >&2 |
|||
fi |
|||
} |
|||
|
|||
|
@ -1,234 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
# Cloudxns.com Domain api |
|||
# |
|||
#CX_Key="1234" |
|||
# |
|||
#CX_Secret="sADDsdasdgdsf" |
|||
|
|||
|
|||
CX_Api="https://www.cloudxns.net/api2" |
|||
|
|||
|
|||
#REST_API |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns-cx-add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ] ; then |
|||
_err "You don't specify cloudxns.com api key or secret yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
REST_API=$CX_Api |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf CX_Key "$CX_Key" |
|||
_saveaccountconf CX_Secret "$CX_Secret" |
|||
|
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root $fulldomain ; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
existing_records $_domain $_sub_domain |
|||
_debug count "$count" |
|||
if [ "$?" != "0" ] ; then |
|||
_err "Error get existing records." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ "$count" == "0" ] ; then |
|||
add_record $_domain $_sub_domain $txtvalue |
|||
else |
|||
update_record $_domain $_sub_domain $txtvalue |
|||
fi |
|||
|
|||
if [ "$?" == "0" ] ; then |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
#usage: root sub |
|||
#return if the sub record already exists. |
|||
#echos the existing records count. |
|||
# '0' means doesn't exist |
|||
existing_records() { |
|||
_debug "Getting txt records" |
|||
root=$1 |
|||
sub=$2 |
|||
|
|||
if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100" ; then |
|||
return 1 |
|||
fi |
|||
count=0 |
|||
seg=$(printf "$response" | grep -o "{[^{]*host\":\"$_sub_domain[^}]*}") |
|||
_debug seg "$seg" |
|||
if [ -z "$seg" ] ; then |
|||
return 0 |
|||
fi |
|||
|
|||
if printf "$response" | grep '"type":"TXT"' > /dev/null ; then |
|||
count=1 |
|||
record_id=$(printf "$seg" | grep -o \"record_id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") |
|||
_debug record_id "$record_id" |
|||
return 0 |
|||
fi |
|||
|
|||
} |
|||
|
|||
#add the txt record. |
|||
#usage: root sub txtvalue |
|||
add_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain=$sub.$root |
|||
|
|||
_info "Adding record" |
|||
|
|||
if ! _rest POST "record" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#update the txt record |
|||
#Usage: root sub txtvalue |
|||
update_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain=$sub.$root |
|||
|
|||
_info "Updating record" |
|||
|
|||
if _rest PUT "record/$record_id" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}" ; then |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
#################### Private functions bellow ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
if ! _rest GET "domain" ; then |
|||
return 1 |
|||
fi |
|||
|
|||
while [ '1' ] ; do |
|||
h=$(printf $domain | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ] ; then |
|||
#not valid |
|||
return 1; |
|||
fi |
|||
|
|||
if printf "$response" | grep "$h." ; then |
|||
seg=$(printf "$response" | grep -o "{[^{]*$h\.[^}]*\}" ) |
|||
_debug seg "$seg" |
|||
_domain_id=$(printf "$seg" | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") |
|||
_debug _domain_id "$_domain_id" |
|||
if [ "$_domain_id" ] ; then |
|||
_sub_domain=$(printf $domain | cut -d . -f 1-$p) |
|||
_debug _sub_domain $_sub_domain |
|||
_domain=$h |
|||
_debug _domain $_domain |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
let "i+=1" |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
|
|||
#Usage: method URI data |
|||
_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
_debug $ep |
|||
url="$REST_API/$ep" |
|||
_debug url "$url" |
|||
|
|||
cdate=$(date -u "+%Y-%m-%d %H:%M:%S UTC") |
|||
_debug cdate "$cdate" |
|||
|
|||
data="$3" |
|||
_debug data "$data" |
|||
|
|||
sec="$CX_Key$url$data$cdate$CX_Secret" |
|||
_debug sec "$sec" |
|||
hmac=$(printf "$sec"| openssl md5 |cut -d " " -f 2) |
|||
_debug hmac "$hmac" |
|||
|
|||
if [ "$3" ] ; then |
|||
response="$(curl --silent -X $m "$url" -H "API-KEY: $CX_Key" -H "API-REQUEST-DATE: $cdate" -H "API-HMAC: $hmac" -H 'Content-Type: application/json' -d "$data")" |
|||
else |
|||
response="$(curl --silent -X $m "$url" -H "API-KEY: $CX_Key" -H "API-REQUEST-DATE: $cdate" -H "API-HMAC: $hmac" -H 'Content-Type: application/json')" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ] ; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug response "$response" |
|||
if ! printf "$response" | grep '"message":"success"' > /dev/null ; then |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
} |
|||
|
|||
|
|||
_debug() { |
|||
|
|||
if [ -z "$DEBUG" ] ; then |
|||
return |
|||
fi |
|||
|
|||
if [ -z "$2" ] ; then |
|||
echo $1 |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_info() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_err() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" >&2 |
|||
else |
|||
echo "$1"="$2" >&2 |
|||
fi |
|||
} |
|||
|
|||
|
@ -1,229 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
# Dnspod.cn Domain api |
|||
# |
|||
#DP_Id="1234" |
|||
# |
|||
#DP_Key="sADDsdasdgdsf" |
|||
|
|||
|
|||
DP_Api="https://dnsapi.cn" |
|||
|
|||
|
|||
#REST_API |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns-dp-add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$DP_Id" ] || [ -z "$DP_Key" ] ; then |
|||
_err "You don't specify dnspod api key and key id yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
REST_API=$DP_Api |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf DP_Id "$DP_Id" |
|||
_saveaccountconf DP_Key "$DP_Key" |
|||
|
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root $fulldomain ; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
existing_records $_domain $_sub_domain |
|||
_debug count "$count" |
|||
if [ "$?" != "0" ] ; then |
|||
_err "Error get existing records." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ "$count" == "0" ] ; then |
|||
add_record $_domain $_sub_domain $txtvalue |
|||
else |
|||
update_record $_domain $_sub_domain $txtvalue |
|||
fi |
|||
} |
|||
|
|||
#usage: root sub |
|||
#return if the sub record already exists. |
|||
#echos the existing records count. |
|||
# '0' means doesn't exist |
|||
existing_records() { |
|||
_debug "Getting txt records" |
|||
root=$1 |
|||
sub=$2 |
|||
|
|||
if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&domain_id=$_domain_id&sub_domain=$_sub_domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if printf "$response" | grep 'No records' ; then |
|||
count=0; |
|||
return 0 |
|||
fi |
|||
|
|||
if printf "$response" | grep "Action completed successful" >/dev/null ; then |
|||
count=$(printf "$response" | grep '<type>TXT</type>' | wc -l) |
|||
|
|||
record_id=$(printf "$response" | grep '^<id>' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) |
|||
return 0 |
|||
else |
|||
_err "get existing records error." |
|||
return 1 |
|||
fi |
|||
|
|||
|
|||
count=0 |
|||
} |
|||
|
|||
#add the txt record. |
|||
#usage: root sub txtvalue |
|||
add_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain=$sub.$root |
|||
|
|||
_info "Adding record" |
|||
|
|||
if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if printf "$response" | grep "Action completed successful" ; then |
|||
|
|||
return 0 |
|||
fi |
|||
|
|||
|
|||
return 1 #error |
|||
} |
|||
|
|||
#update the txt record |
|||
#Usage: root sub txtvalue |
|||
update_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain=$sub.$root |
|||
|
|||
_info "Updating record" |
|||
|
|||
if ! _rest POST "Record.Modify" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认&record_id=$record_id"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if printf "$response" | grep "Action completed successful" ; then |
|||
|
|||
return 0 |
|||
fi |
|||
|
|||
return 1 #error |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
#################### Private functions bellow ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while [ '1' ] ; do |
|||
h=$(printf $domain | cut -d . -f $i-100) |
|||
if [ -z "$h" ] ; then |
|||
#not valid |
|||
return 1; |
|||
fi |
|||
|
|||
if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if printf "$response" | grep "Action completed successful" ; then |
|||
_domain_id=$(printf "$response" | grep -o \"id\":\"[^\"]*\" | cut -d : -f 2 | tr -d \") |
|||
_debug _domain_id "$_domain_id" |
|||
if [ "$_domain_id" ] ; then |
|||
_sub_domain=$(printf $domain | cut -d . -f 1-$p) |
|||
_debug _sub_domain $_sub_domain |
|||
_domain=$h |
|||
_debug _domain $_domain |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
let "i+=1" |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
|
|||
#Usage: method URI data |
|||
_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
_debug $ep |
|||
url="$REST_API/$ep" |
|||
|
|||
_debug url "$url" |
|||
|
|||
if [ "$3" ] ; then |
|||
data="$3" |
|||
_debug data "$data" |
|||
response="$(curl --silent -X $m "$url" -d $data)" |
|||
else |
|||
response="$(curl --silent -X $m "$url" )" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ] ; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug response "$response" |
|||
return 0 |
|||
} |
|||
|
|||
|
|||
_debug() { |
|||
|
|||
if [ -z "$DEBUG" ] ; then |
|||
return |
|||
fi |
|||
|
|||
if [ -z "$2" ] ; then |
|||
echo $1 |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_info() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_err() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" >&2 |
|||
else |
|||
echo "$1"="$2" >&2 |
|||
fi |
|||
} |
|||
|
|||
|
@ -1,61 +0,0 @@ |
|||
#!/bin/bash |
|||
|
|||
#Here is a sample custom api script. |
|||
#This file name is "dns-myapi.sh" |
|||
#So, here must be a method dns-myapi-add() |
|||
#Which will be called by le.sh to add the txt record to your api system. |
|||
#returns 0 meanst success, otherwise error. |
|||
|
|||
|
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns-myapi-add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_err "Not implemented!" |
|||
return 1; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
#################### Private functions bellow ################################## |
|||
|
|||
|
|||
_debug() { |
|||
|
|||
if [ -z "$DEBUG" ] ; then |
|||
return |
|||
fi |
|||
|
|||
if [ -z "$2" ] ; then |
|||
echo $1 |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_info() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" |
|||
else |
|||
echo "$1"="$2" |
|||
fi |
|||
} |
|||
|
|||
_err() { |
|||
if [ -z "$2" ] ; then |
|||
echo "$1" >&2 |
|||
else |
|||
echo "$1"="$2" >&2 |
|||
fi |
|||
} |
|||
|
|||
|
@ -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 |
|||
} |
@ -0,0 +1,202 @@ |
|||
#!/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 |
|||
|
|||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" |
|||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" |
|||
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_mutable Ali_Key "$Ali_Key" |
|||
_saveaccountconf_mutable 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 |
|||
txtvalue=$2 |
|||
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" |
|||
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
_clean |
|||
} |
|||
|
|||
#################### 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 |
|||
|
|||
_debug2 response "$response" |
|||
if [ -z "$2" ]; then |
|||
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" |
|||
if [ "$message" ]; then |
|||
_err "$message" |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
_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() { |
|||
_qdomain="$1" |
|||
_qsubdomain="$2" |
|||
query='' |
|||
query=$query'AccessKeyId='$Ali_Key |
|||
query=$query'&Action=DescribeDomainRecords' |
|||
query=$query'&DomainName='$_qdomain |
|||
query=$query'&Format=json' |
|||
query=$query'&RRKeyWord='$_qsubdomain |
|||
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" "$_sub_domain" |
|||
if ! _ali_rest "Check exist records" "ignore"; then |
|||
return 1 |
|||
fi |
|||
|
|||
record_id="$(echo "$response" | tr '{' "\n" | grep "$_sub_domain" | grep "$txtvalue" | tr "," "\n" | grep RecordId | cut -d '"' -f 4)" |
|||
_debug2 record_id "$record_id" |
|||
|
|||
if [ -z "$record_id" ]; then |
|||
_debug "record not found, skip" |
|||
else |
|||
_delete_record_query "$record_id" |
|||
_ali_rest "Delete record $record_id" "ignore" |
|||
fi |
|||
|
|||
} |
|||
|
|||
_timestamp() { |
|||
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ" |
|||
} |
@ -0,0 +1,264 @@ |
|||
#!/usr/bin/env sh |
|||
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- |
|||
|
|||
# This is the InternetX autoDNS xml api wrapper for acme.sh |
|||
# Author: auerswald@gmail.com |
|||
# Created: 2018-01-14 |
|||
# |
|||
# export AUTODNS_USER="username" |
|||
# export AUTODNS_PASSWORD="password" |
|||
# export AUTODNS_CONTEXT="context" |
|||
# |
|||
# Usage: |
|||
# acme.sh --issue --dns dns_autodns -d example.com |
|||
|
|||
AUTODNS_API="https://gateway.autodns.com" |
|||
|
|||
# Arguments: |
|||
# txtdomain |
|||
# txt |
|||
dns_autodns_add() { |
|||
fulldomain="$1" |
|||
txtvalue="$2" |
|||
|
|||
AUTODNS_USER="${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}" |
|||
AUTODNS_PASSWORD="${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}" |
|||
AUTODNS_CONTEXT="${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}" |
|||
|
|||
if [ -z "$AUTODNS_USER" ] || [ -z "$AUTODNS_CONTEXT" ] || [ -z "$AUTODNS_PASSWORD" ]; then |
|||
_err "You don't specify autodns user, password and context." |
|||
return 1 |
|||
fi |
|||
|
|||
_saveaccountconf_mutable AUTODNS_USER "$AUTODNS_USER" |
|||
_saveaccountconf_mutable AUTODNS_PASSWORD "$AUTODNS_PASSWORD" |
|||
_saveaccountconf_mutable AUTODNS_CONTEXT "$AUTODNS_CONTEXT" |
|||
|
|||
_debug "First detect the root zone" |
|||
|
|||
if ! _get_autodns_zone "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _zone "$_zone" |
|||
_debug _system_ns "$_system_ns" |
|||
|
|||
_info "Adding TXT record" |
|||
|
|||
autodns_response="$(_autodns_zone_update "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" |
|||
|
|||
if [ "$?" -eq "0" ]; then |
|||
_info "Added, OK" |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
# Arguments: |
|||
# txtdomain |
|||
# txt |
|||
dns_autodns_rm() { |
|||
fulldomain="$1" |
|||
txtvalue="$2" |
|||
|
|||
AUTODNS_USER="${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}" |
|||
AUTODNS_PASSWORD="${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}" |
|||
AUTODNS_CONTEXT="${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}" |
|||
|
|||
if [ -z "$AUTODNS_USER" ] || [ -z "$AUTODNS_CONTEXT" ] || [ -z "$AUTODNS_PASSWORD" ]; then |
|||
_err "You don't specify autodns user, password and context." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug "First detect the root zone" |
|||
|
|||
if ! _get_autodns_zone "$fulldomain"; then |
|||
_err "zone not found" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _zone "$_zone" |
|||
_debug _system_ns "$_system_ns" |
|||
|
|||
_info "Delete TXT record" |
|||
|
|||
autodns_response="$(_autodns_zone_cleanup "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" |
|||
|
|||
if [ "$?" -eq "0" ]; then |
|||
_info "Deleted, OK" |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
# Arguments: |
|||
# fulldomain |
|||
# Returns: |
|||
# _sub_domain=_acme-challenge.www |
|||
# _zone=domain.com |
|||
# _system_ns |
|||
_get_autodns_zone() { |
|||
domain="$1" |
|||
|
|||
i=2 |
|||
p=1 |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
|
|||
if [ -z "$h" ]; then |
|||
# not valid |
|||
return 1 |
|||
fi |
|||
|
|||
autodns_response="$(_autodns_zone_inquire "$h")" |
|||
|
|||
if [ "$?" -ne "0" ]; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$autodns_response" "<summary>1</summary>" >/dev/null; then |
|||
_zone="$(echo "$autodns_response" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
_system_ns="$(echo "$autodns_response" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
return 0 |
|||
fi |
|||
|
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_build_request_auth_xml() { |
|||
printf "<auth> |
|||
<user>%s</user> |
|||
<password>%s</password> |
|||
<context>%s</context> |
|||
</auth>" "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT" |
|||
} |
|||
|
|||
# Arguments: |
|||
# zone |
|||
_build_zone_inquire_xml() { |
|||
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?> |
|||
<request> |
|||
%s |
|||
<task> |
|||
<code>0205</code> |
|||
<view> |
|||
<children>1</children> |
|||
<limit>1</limit> |
|||
</view> |
|||
<where> |
|||
<key>name</key> |
|||
<operator>eq</operator> |
|||
<value>%s</value> |
|||
</where> |
|||
</task> |
|||
</request>" "$(_build_request_auth_xml)" "$1" |
|||
} |
|||
|
|||
# Arguments: |
|||
# zone |
|||
# subdomain |
|||
# txtvalue |
|||
# system_ns |
|||
_build_zone_update_xml() { |
|||
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?> |
|||
<request> |
|||
%s |
|||
<task> |
|||
<code>0202001</code> |
|||
<default> |
|||
<rr_add> |
|||
<name>%s</name> |
|||
<ttl>600</ttl> |
|||
<type>TXT</type> |
|||
<value>%s</value> |
|||
</rr_add> |
|||
</default> |
|||
<zone> |
|||
<name>%s</name> |
|||
<system_ns>%s</system_ns> |
|||
</zone> |
|||
</task> |
|||
</request>" "$(_build_request_auth_xml)" "$2" "$3" "$1" "$4" |
|||
} |
|||
|
|||
# Arguments: |
|||
# zone |
|||
_autodns_zone_inquire() { |
|||
request_data="$(_build_zone_inquire_xml "$1")" |
|||
autodns_response="$(_autodns_api_call "$request_data")" |
|||
ret="$?" |
|||
|
|||
printf "%s" "$autodns_response" |
|||
return "$ret" |
|||
} |
|||
|
|||
# Arguments: |
|||
# zone |
|||
# subdomain |
|||
# txtvalue |
|||
# system_ns |
|||
_autodns_zone_update() { |
|||
request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" |
|||
autodns_response="$(_autodns_api_call "$request_data")" |
|||
ret="$?" |
|||
|
|||
printf "%s" "$autodns_response" |
|||
return "$ret" |
|||
} |
|||
|
|||
# Arguments: |
|||
# zone |
|||
# subdomain |
|||
# txtvalue |
|||
# system_ns |
|||
_autodns_zone_cleanup() { |
|||
request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" |
|||
# replace 'rr_add>' with 'rr_rem>' in request_data |
|||
request_data="$(printf -- "%s" "$request_data" | sed 's/rr_add>/rr_rem>/g')" |
|||
autodns_response="$(_autodns_api_call "$request_data")" |
|||
ret="$?" |
|||
|
|||
printf "%s" "$autodns_response" |
|||
return "$ret" |
|||
} |
|||
|
|||
# Arguments: |
|||
# request_data |
|||
_autodns_api_call() { |
|||
request_data="$1" |
|||
|
|||
_debug request_data "$request_data" |
|||
|
|||
autodns_response="$(_post "$request_data" "$AUTODNS_API")" |
|||
ret="$?" |
|||
|
|||
_debug autodns_response "$autodns_response" |
|||
|
|||
if [ "$ret" -ne "0" ]; then |
|||
_err "error" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$autodns_response" "<type>success</type>" >/dev/null; then |
|||
_info "success" |
|||
printf "%s" "$autodns_response" |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
@ -0,0 +1,340 @@ |
|||
#!/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 |
|||
|
|||
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" |
|||
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" |
|||
|
|||
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then |
|||
_use_container_role || _use_instance_role |
|||
fi |
|||
|
|||
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then |
|||
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 your key and try again. see $(__green $AWS_WIKI)" |
|||
return 1 |
|||
fi |
|||
|
|||
#save for future use, unless using a role which will be fetched as needed |
|||
if [ -z "$_using_role" ]; then |
|||
_saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" |
|||
_saveaccountconf_mutable AWS_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" |
|||
|
|||
_info "Geting existing records for $fulldomain" |
|||
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "<Name>$fulldomain.</Name>"; then |
|||
_resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")" |
|||
_debug "_resource_record" "$_resource_record" |
|||
else |
|||
_debug "single new add" |
|||
fi |
|||
|
|||
if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then |
|||
_info "The txt record already exists, skip" |
|||
return 0 |
|||
fi |
|||
|
|||
_debug "Adding records" |
|||
|
|||
_aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" |
|||
|
|||
if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then |
|||
_info "txt record updated success." |
|||
return 0 |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_aws_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" |
|||
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" |
|||
|
|||
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then |
|||
_use_container_role || _use_instance_role |
|||
fi |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _domain_id "$_domain_id" |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_info "Geting existing records for $fulldomain" |
|||
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "<Name>$fulldomain.</Name>"; then |
|||
_resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")" |
|||
_debug "_resource_record" "$_resource_record" |
|||
else |
|||
_debug "no records exists, skip" |
|||
return 0 |
|||
fi |
|||
|
|||
_aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>" |
|||
|
|||
if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then |
|||
_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 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug2 "Checking domain: $h" |
|||
if [ -z "$h" ]; then |
|||
if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then |
|||
_debug "IsTruncated" |
|||
_nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
_debug "NextMarker" "$_nextMarker" |
|||
if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then |
|||
_debug "Truncated request OK" |
|||
i=2 |
|||
p=1 |
|||
continue |
|||
else |
|||
_err "Truncated request error." |
|||
fi |
|||
fi |
|||
#not valid |
|||
_err "Invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "<Name>$h.</Name>"; then |
|||
hostedzone="$(echo "$response" | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")" |
|||
_debug hostedzone "$hostedzone" |
|||
if [ "$hostedzone" ]; then |
|||
_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 |
|||
_err "Can not find domain id: $h" |
|||
return 1 |
|||
fi |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
_use_container_role() { |
|||
# automatically set if running inside ECS |
|||
if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then |
|||
_debug "No ECS environment variable detected" |
|||
return 1 |
|||
fi |
|||
_use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" |
|||
} |
|||
|
|||
_use_instance_role() { |
|||
_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" |
|||
_debug "_url" "$_url" |
|||
if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then |
|||
_debug "Unable to fetch IAM role from instance metadata" |
|||
return 1 |
|||
fi |
|||
_aws_role=$(_get "$_url" "" 1) |
|||
_debug "_aws_role" "$_aws_role" |
|||
_use_metadata "$_url$_aws_role" |
|||
} |
|||
|
|||
_use_metadata() { |
|||
_aws_creds="$( |
|||
_get "$1" "" 1 \ |
|||
| _normalizeJson \ |
|||
| tr '{,}' '\n' \ |
|||
| while read -r _line; do |
|||
_key="$(echo "${_line%%:*}" | tr -d '"')" |
|||
_value="${_line#*:}" |
|||
_debug3 "_key" "$_key" |
|||
_secure_debug3 "_value" "$_value" |
|||
case "$_key" in |
|||
AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; |
|||
SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; |
|||
Token) echo "AWS_SESSION_TOKEN=$_value" ;; |
|||
esac |
|||
done \ |
|||
| paste -sd' ' - |
|||
)" |
|||
_secure_debug "_aws_creds" "$_aws_creds" |
|||
|
|||
if [ -z "$_aws_creds" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
eval "$_aws_creds" |
|||
_using_role=true |
|||
} |
|||
|
|||
#method uri qstr data |
|||
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 _H3="x-amz-security-token: $AWS_SESSION_TOKEN" |
|||
CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" |
|||
SignedHeaders="${SignedHeaders};x-amz-security-token" |
|||
fi |
|||
_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" ############################ |
|||
|
|||
_secure_debug2 kSecret "$kSecret" |
|||
|
|||
kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" |
|||
_secure_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 "%s" "aws4_request" | _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" |
|||
|
|||
_H2="Authorization: $Authorization" |
|||
_debug _H2 "$_H2" |
|||
|
|||
url="$AWS_URL/$ep" |
|||
if [ "$qsr" ]; then |
|||
url="$AWS_URL/$ep?$qsr" |
|||
fi |
|||
|
|||
if [ "$mtd" = "GET" ]; then |
|||
response="$(_get "$url")" |
|||
else |
|||
response="$(_post "$data" "$url")" |
|||
fi |
|||
|
|||
_ret="$?" |
|||
_debug2 response "$response" |
|||
if [ "$_ret" = "0" ]; then |
|||
if _contains "$response" "<ErrorResponse"; then |
|||
_err "Response error:$response" |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
return "$_ret" |
|||
} |
@ -0,0 +1,339 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Azure-DNS" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
# Used to add txt record |
|||
# |
|||
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate |
|||
# |
|||
dns_azure_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}" |
|||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" |
|||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" |
|||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" |
|||
|
|||
if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Subscription ID " |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_TENANTID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Tenant ID " |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_APPID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure App ID" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Client Secret" |
|||
return 1 |
|||
fi |
|||
#save account details to account conf file. |
|||
_saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID" |
|||
_saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID" |
|||
_saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID" |
|||
_saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET" |
|||
|
|||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") |
|||
|
|||
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _domain_id "$_domain_id" |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" |
|||
_debug "$acmeRecordURI" |
|||
# Get existing TXT record |
|||
_azure_rest GET "$acmeRecordURI" "" "$accesstoken" |
|||
values="{\"value\":[\"$txtvalue\"]}" |
|||
timestamp="$(_time)" |
|||
if [ "$_code" = "200" ]; then |
|||
vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"")" |
|||
_debug "existing TXT found" |
|||
_debug "$vlist" |
|||
existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\s*:\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")" |
|||
if [ -z "$existingts" ]; then |
|||
# the record was not created by acme.sh. Copy the exisiting entires |
|||
existingts=$timestamp |
|||
fi |
|||
_diff="$(_math "$timestamp - $existingts")" |
|||
_debug "existing txt age: $_diff" |
|||
# only use recently added records and discard if older than 2 hours because they are probably orphaned |
|||
if [ "$_diff" -lt 7200 ]; then |
|||
_debug "existing txt value: $vlist" |
|||
for v in $vlist; do |
|||
values="$values ,{\"value\":[\"$v\"]}" |
|||
done |
|||
fi |
|||
fi |
|||
# Add the txtvalue TXT Record |
|||
body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" |
|||
_azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" |
|||
if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then |
|||
_info "validation value added" |
|||
else |
|||
_err "error adding validation value ($_code)" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
# Usage: fulldomain txtvalue |
|||
# Used to remove the txt record after validation |
|||
# |
|||
# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete |
|||
# |
|||
dns_azure_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}" |
|||
AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" |
|||
AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" |
|||
AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" |
|||
|
|||
if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Subscription ID " |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_TENANTID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Tenant ID " |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_APPID" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure App ID" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$AZUREDNS_CLIENTSECRET" ]; then |
|||
AZUREDNS_SUBSCRIPTIONID="" |
|||
AZUREDNS_TENANTID="" |
|||
AZUREDNS_APPID="" |
|||
AZUREDNS_CLIENTSECRET="" |
|||
_err "You didn't specify the Azure Client Secret" |
|||
return 1 |
|||
fi |
|||
|
|||
accesstoken=$(_azure_getaccess_token "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") |
|||
|
|||
if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _domain_id "$_domain_id" |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" |
|||
_debug "$acmeRecordURI" |
|||
# Get existing TXT record |
|||
_azure_rest GET "$acmeRecordURI" "" "$accesstoken" |
|||
timestamp="$(_time)" |
|||
if [ "$_code" = "200" ]; then |
|||
vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")" |
|||
values="" |
|||
comma="" |
|||
for v in $vlist; do |
|||
values="$values$comma{\"value\":[\"$v\"]}" |
|||
comma="," |
|||
done |
|||
if [ -z "$values" ]; then |
|||
# No values left remove record |
|||
_debug "removing validation record completely $acmeRecordURI" |
|||
_azure_rest DELETE "$acmeRecordURI" "" "$accesstoken" |
|||
if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then |
|||
_info "validation record removed" |
|||
else |
|||
_err "error removing validation record ($_code)" |
|||
return 1 |
|||
fi |
|||
else |
|||
# Remove only txtvalue from the TXT Record |
|||
body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" |
|||
_azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" |
|||
if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then |
|||
_info "validation value removed" |
|||
else |
|||
_err "error removing validation value ($_code)" |
|||
return 1 |
|||
fi |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
################### Private functions below ################################## |
|||
|
|||
_azure_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
accesstoken="$4" |
|||
|
|||
MAX_REQUEST_RETRY_TIMES=5 |
|||
_request_retry_times=0 |
|||
while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do |
|||
_debug3 _request_retry_times "$_request_retry_times" |
|||
export _H1="authorization: Bearer $accesstoken" |
|||
export _H2="accept: application/json" |
|||
export _H3="Content-Type: application/json" |
|||
# clear headers from previous request to avoid getting wrong http code on timeouts |
|||
:>"$HTTP_HEADER" |
|||
_debug "$ep" |
|||
if [ "$m" != "GET" ]; then |
|||
_secure_debug2 "data $data" |
|||
response="$(_post "$data" "$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$ep")" |
|||
fi |
|||
_secure_debug2 "response $response" |
|||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" |
|||
_debug "http response code $_code" |
|||
if [ "$_code" = "401" ]; then |
|||
# we have an invalid access token set to expired |
|||
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0" |
|||
_err "access denied make sure your Azure settings are correct. See $WIKI" |
|||
return 1 |
|||
fi |
|||
# See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes |
|||
if [ "$?" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then |
|||
_request_retry_times="$(_math "$_request_retry_times" + 1)" |
|||
_info "REST call error $_code retrying $ep in $_request_retry_times s" |
|||
_sleep "$_request_retry_times" |
|||
continue |
|||
fi |
|||
break |
|||
done |
|||
if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then |
|||
_err "Error Azure REST called was retried $MAX_REQUEST_RETRY_TIMES times." |
|||
_err "Calling $ep failed." |
|||
return 1 |
|||
fi |
|||
response="$(echo "$response" | _normalizeJson)" |
|||
return 0 |
|||
} |
|||
|
|||
## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token |
|||
_azure_getaccess_token() { |
|||
tenantID=$1 |
|||
clientID=$2 |
|||
clientSecret=$3 |
|||
|
|||
accesstoken="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" |
|||
expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}" |
|||
|
|||
# can we reuse the bearer token? |
|||
if [ -n "$accesstoken" ] && [ -n "$expires_on" ]; then |
|||
if [ "$(_time)" -lt "$expires_on" ]; then |
|||
# brearer token is still valid - reuse it |
|||
_debug "reusing bearer token" |
|||
printf "%s" "$accesstoken" |
|||
return 0 |
|||
else |
|||
_debug "bearer token expired" |
|||
fi |
|||
fi |
|||
_debug "getting new bearer token" |
|||
|
|||
export _H1="accept: application/json" |
|||
export _H2="Content-Type: application/x-www-form-urlencoded" |
|||
|
|||
body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials" |
|||
_secure_debug2 "data $body" |
|||
response="$(_post "$body" "https://login.microsoftonline.com/$tenantID/oauth2/token" "" "POST")" |
|||
_secure_debug2 "response $response" |
|||
response="$(echo "$response" | _normalizeJson)" |
|||
accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") |
|||
expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") |
|||
|
|||
if [ -z "$accesstoken" ]; then |
|||
_err "no acccess token received. Check your Azure settings see $WIKI" |
|||
return 1 |
|||
fi |
|||
if [ "$?" != "0" ]; then |
|||
_err "error $response" |
|||
return 1 |
|||
fi |
|||
_saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$accesstoken" |
|||
_saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on" |
|||
printf "%s" "$accesstoken" |
|||
return 0 |
|||
} |
|||
|
|||
_get_root() { |
|||
domain=$1 |
|||
subscriptionId=$2 |
|||
accesstoken=$3 |
|||
i=2 |
|||
p=1 |
|||
|
|||
## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list |
|||
## returns up to 100 zones in one response therefore handling more results is not not implemented |
|||
## (ZoneListResult with continuation token for the next page of results) |
|||
## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways |
|||
## |
|||
_azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?api-version=2017-09-01" "" "$accesstoken" |
|||
# Find matching domain name is Json response |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug2 "Checking domain: $h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
_err "Invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then |
|||
_domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \") |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
@ -0,0 +1,199 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#CF_Email="xxxx@sss.com" |
|||
|
|||
CF_Api="https://api.cloudflare.com/client/v4" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_cf_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" |
|||
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" |
|||
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then |
|||
CF_Key="" |
|||
CF_Email="" |
|||
_err "You don't specify cloudflare api key and email yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
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. |
|||
_saveaccountconf_mutable CF_Key "$CF_Key" |
|||
_saveaccountconf_mutable CF_Email "$CF_Email" |
|||
|
|||
_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" |
|||
|
|||
if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
# For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so |
|||
# we can not use updating anymore. |
|||
# count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) |
|||
# _debug count "$count" |
|||
# if [ "$count" = "0" ]; then |
|||
_info "Adding record" |
|||
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 |
|||
_info "Added, OK" |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
return 1 |
|||
# else |
|||
# _info "Updating record" |
|||
# record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) |
|||
# _debug "record_id" "$record_id" |
|||
# |
|||
# _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 |
|||
# _info "Updated, OK" |
|||
# return 0 |
|||
# fi |
|||
# _err "Update error" |
|||
# return 1 |
|||
# fi |
|||
|
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_cf_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" |
|||
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" |
|||
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then |
|||
CF_Key="" |
|||
CF_Email="" |
|||
_err "You don't specify cloudflare api key and email yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
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" |
|||
|
|||
_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 below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _cf_rest GET "zones?name=$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then |
|||
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_cf_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="X-Auth-Email: $CF_Email" |
|||
export _H2="X-Auth-Key: $CF_Key" |
|||
export _H3="Content-Type: application/json" |
|||
|
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$CF_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$CF_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,184 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Author: Boyan Peychev <boyan at cloudns dot net> |
|||
# Repository: https://github.com/ClouDNS/acme.sh/ |
|||
|
|||
#CLOUDNS_AUTH_ID=XXXXX |
|||
#CLOUDNS_SUB_AUTH_ID=XXXXX |
|||
#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" |
|||
CLOUDNS_API="https://api.cloudns.net" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_cloudns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_cloudns_add() { |
|||
_info "Using cloudns" |
|||
|
|||
if ! _dns_cloudns_init_check; then |
|||
return 1 |
|||
fi |
|||
|
|||
zone="$(_dns_cloudns_get_zone_name "$1")" |
|||
if [ -z "$zone" ]; then |
|||
_err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." |
|||
return 1 |
|||
fi |
|||
|
|||
host="$(echo "$1" | sed "s/\.$zone\$//")" |
|||
record=$2 |
|||
|
|||
_debug zone "$zone" |
|||
_debug host "$host" |
|||
_debug record "$record" |
|||
|
|||
_info "Adding the TXT record for $1" |
|||
_dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60" |
|||
if ! _contains "$response" "\"status\":\"Success\""; then |
|||
_err "Record cannot be added." |
|||
return 1 |
|||
fi |
|||
_info "Added." |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#Usage: dns_cloudns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_cloudns_rm() { |
|||
_info "Using cloudns" |
|||
|
|||
if ! _dns_cloudns_init_check; then |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$zone" ]; then |
|||
zone="$(_dns_cloudns_get_zone_name "$1")" |
|||
if [ -z "$zone" ]; then |
|||
_err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
host="$(echo "$1" | sed "s/\.$zone\$//")" |
|||
record=$2 |
|||
|
|||
_dns_cloudns_http_api_call "dns/records.json" "domain-name=$zone&host=$host&type=TXT" |
|||
if ! _contains "$response" "\"id\":"; then |
|||
return 1 |
|||
fi |
|||
|
|||
for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do |
|||
record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g') |
|||
|
|||
if [ ! -z "$record_id" ]; then |
|||
_debug zone "$zone" |
|||
_debug host "$host" |
|||
_debug record "$record" |
|||
_debug record_id "$record_id" |
|||
|
|||
_info "Deleting the TXT record for $1" |
|||
_dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" |
|||
|
|||
if ! _contains "$response" "\"status\":\"Success\""; then |
|||
_err "The TXT record for $1 cannot be deleted." |
|||
else |
|||
_info "Deleted." |
|||
fi |
|||
fi |
|||
done |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
_dns_cloudns_init_check() { |
|||
if [ ! -z "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then |
|||
return 0 |
|||
fi |
|||
|
|||
CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}" |
|||
CLOUDNS_SUB_AUTH_ID="${CLOUDNS_SUB_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_SUB_AUTH_ID)}" |
|||
CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}" |
|||
if [ -z "$CLOUDNS_AUTH_ID$CLOUDNS_SUB_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then |
|||
CLOUDNS_AUTH_ID="" |
|||
CLOUDNS_SUB_AUTH_ID="" |
|||
CLOUDNS_AUTH_PASSWORD="" |
|||
_err "You don't specify cloudns api id and password yet." |
|||
_err "Please create you id and password and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$CLOUDNS_AUTH_ID" ] && [ -z "$CLOUDNS_SUB_AUTH_ID" ]; then |
|||
_err "CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID is not configured" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then |
|||
_err "CLOUDNS_AUTH_PASSWORD is not configured" |
|||
return 1 |
|||
fi |
|||
|
|||
_dns_cloudns_http_api_call "dns/login.json" "" |
|||
|
|||
if ! _contains "$response" "\"status\":\"Success\""; then |
|||
_err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." |
|||
return 1 |
|||
fi |
|||
|
|||
# save the api id and password to the account conf file. |
|||
_saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" |
|||
_saveaccountconf_mutable CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" |
|||
_saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" |
|||
|
|||
CLOUDNS_INIT_CHECK_COMPLETED=1 |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_dns_cloudns_get_zone_name() { |
|||
i=2 |
|||
while true; do |
|||
zoneForCheck=$(printf "%s" "$1" | cut -d . -f $i-100) |
|||
|
|||
if [ -z "$zoneForCheck" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
_debug zoneForCheck "$zoneForCheck" |
|||
|
|||
_dns_cloudns_http_api_call "dns/get-zone-info.json" "domain-name=$zoneForCheck" |
|||
|
|||
if ! _contains "$response" "\"status\":\"Failed\""; then |
|||
echo "$zoneForCheck" |
|||
return 0 |
|||
fi |
|||
|
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_dns_cloudns_http_api_call() { |
|||
method=$1 |
|||
|
|||
_debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" |
|||
_debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" |
|||
_debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" |
|||
|
|||
if [ ! -z "$CLOUDNS_SUB_AUTH_ID" ]; then |
|||
auth_user="sub-auth-id=$CLOUDNS_SUB_AUTH_ID" |
|||
else |
|||
auth_user="auth-id=$CLOUDNS_AUTH_ID" |
|||
fi |
|||
|
|||
if [ -z "$2" ]; then |
|||
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD" |
|||
else |
|||
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" |
|||
fi |
|||
|
|||
response="$(_get "$CLOUDNS_API/$method?$data")" |
|||
|
|||
_debug response "$response" |
|||
|
|||
return 0 |
|||
} |
@ -0,0 +1,181 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Cloudxns.com Domain api |
|||
# |
|||
#CX_Key="1234" |
|||
# |
|||
#CX_Secret="sADDsdasdgdsf" |
|||
|
|||
CX_Api="https://www.cloudxns.net/api2" |
|||
|
|||
#REST_API |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_cx_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then |
|||
CX_Key="" |
|||
CX_Secret="" |
|||
_err "You don't specify cloudxns.com api key or secret yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
REST_API="$CX_Api" |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf CX_Key "$CX_Key" |
|||
_saveaccountconf CX_Secret "$CX_Secret" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
add_record "$_domain" "$_sub_domain" "$txtvalue" |
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_cx_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
REST_API="$CX_Api" |
|||
if _get_root "$fulldomain"; then |
|||
record_id="" |
|||
existing_records "$_domain" "$_sub_domain" "$txtvalue" |
|||
if [ "$record_id" ]; then |
|||
_rest DELETE "record/$record_id/$_domain_id" "{}" |
|||
_info "Deleted record ${fulldomain}" |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
#usage: root sub |
|||
#return if the sub record already exists. |
|||
#echos the existing records count. |
|||
# '0' means doesn't exist |
|||
existing_records() { |
|||
_debug "Getting txt records" |
|||
root=$1 |
|||
sub=$2 |
|||
if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then |
|||
return 1 |
|||
fi |
|||
|
|||
seg=$(printf "%s\n" "$response" | _egrep_o '"record_id":[^{]*host":"'"$_sub_domain"'"[^}]*\}') |
|||
_debug seg "$seg" |
|||
if [ -z "$seg" ]; then |
|||
return 0 |
|||
fi |
|||
|
|||
if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then |
|||
record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) |
|||
_debug record_id "$record_id" |
|||
return 0 |
|||
fi |
|||
|
|||
} |
|||
|
|||
#add the txt record. |
|||
#usage: root sub txtvalue |
|||
add_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain="$sub.$root" |
|||
|
|||
_info "Adding record" |
|||
|
|||
if ! _rest POST "record" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
if ! _rest GET "domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "$h."; then |
|||
seg=$(printf "%s\n" "$response" | _egrep_o '"id":[^{]*"'"$h"'."[^}]*}') |
|||
_debug seg "$seg" |
|||
_domain_id=$(printf "%s\n" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") |
|||
_debug _domain_id "$_domain_id" |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_debug _sub_domain "$_sub_domain" |
|||
_domain="$h" |
|||
_debug _domain "$_domain" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p="$i" |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
#Usage: method URI data |
|||
_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
_debug ep "$ep" |
|||
url="$REST_API/$ep" |
|||
_debug url "$url" |
|||
|
|||
cdate=$(date -u "+%Y-%m-%d %H:%M:%S UTC") |
|||
_debug cdate "$cdate" |
|||
|
|||
data="$3" |
|||
_debug data "$data" |
|||
|
|||
sec="$CX_Key$url$data$cdate$CX_Secret" |
|||
_debug sec "$sec" |
|||
hmac=$(printf "%s" "$sec" | _digest md5 hex) |
|||
_debug hmac "$hmac" |
|||
|
|||
export _H1="API-KEY: $CX_Key" |
|||
export _H2="API-REQUEST-DATE: $cdate" |
|||
export _H3="API-HMAC: $hmac" |
|||
export _H4="Content-Type: application/json" |
|||
|
|||
if [ "$data" ]; then |
|||
response="$(_post "$data" "$url" "" "$m")" |
|||
else |
|||
response="$(_get "$url")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
|
|||
_contains "$response" '"code":1' |
|||
|
|||
} |
@ -0,0 +1,328 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
######## |
|||
# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh) |
|||
# |
|||
# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com |
|||
# |
|||
# Dependencies: |
|||
# ------------- |
|||
# - oathtool (When using 2 Factor Authentication) |
|||
# |
|||
# Issues: |
|||
# ------- |
|||
# Any issues / questions / suggestions can be posted here: |
|||
# https://github.com/noplanman/cyon-api/issues |
|||
# |
|||
# Author: Armando Lüscher <armando@noplanman.ch> |
|||
######## |
|||
|
|||
dns_cyon_add() { |
|||
_cyon_load_credentials \ |
|||
&& _cyon_load_parameters "$@" \ |
|||
&& _cyon_print_header "add" \ |
|||
&& _cyon_login \ |
|||
&& _cyon_change_domain_env \ |
|||
&& _cyon_add_txt \ |
|||
&& _cyon_logout |
|||
} |
|||
|
|||
dns_cyon_rm() { |
|||
_cyon_load_credentials \ |
|||
&& _cyon_load_parameters "$@" \ |
|||
&& _cyon_print_header "delete" \ |
|||
&& _cyon_login \ |
|||
&& _cyon_change_domain_env \ |
|||
&& _cyon_delete_txt \ |
|||
&& _cyon_logout |
|||
} |
|||
|
|||
######################### |
|||
### PRIVATE FUNCTIONS ### |
|||
######################### |
|||
|
|||
_cyon_load_credentials() { |
|||
# Convert loaded password to/from base64 as needed. |
|||
if [ "${CY_Password_B64}" ]; then |
|||
CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64 "multiline")" |
|||
elif [ "${CY_Password}" ]; then |
|||
CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)" |
|||
fi |
|||
|
|||
if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then |
|||
# Dummy entries to satisfy script checker. |
|||
CY_Username="" |
|||
CY_Password="" |
|||
CY_OTP_Secret="" |
|||
|
|||
_err "" |
|||
_err "You haven't set your cyon.ch login credentials yet." |
|||
_err "Please set the required cyon environment variables." |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
# Save the login credentials to the account.conf file. |
|||
_debug "Save credentials to account.conf" |
|||
_saveaccountconf CY_Username "${CY_Username}" |
|||
_saveaccountconf CY_Password_B64 "$CY_Password_B64" |
|||
if [ ! -z "${CY_OTP_Secret}" ]; then |
|||
_saveaccountconf CY_OTP_Secret "$CY_OTP_Secret" |
|||
else |
|||
_clearaccountconf CY_OTP_Secret |
|||
fi |
|||
} |
|||
|
|||
_cyon_is_idn() { |
|||
_idn_temp="$(printf "%s" "${1}" | tr -d "0-9a-zA-Z.,-_")" |
|||
_idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" |
|||
[ "$_idn_temp" ] || [ "$_idn_temp2" ] |
|||
} |
|||
|
|||
_cyon_load_parameters() { |
|||
# Read the required parameters to add the TXT entry. |
|||
# shellcheck disable=SC2018,SC2019 |
|||
fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")" |
|||
fulldomain_idn="${fulldomain}" |
|||
|
|||
# Special case for IDNs, as cyon needs a domain environment change, |
|||
# which uses the "pretty" instead of the punycode version. |
|||
if _cyon_is_idn "${fulldomain}"; then |
|||
if ! _exists idn; then |
|||
_err "Please install idn to process IDN names." |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
fulldomain="$(idn -u "${fulldomain}")" |
|||
fulldomain_idn="$(idn -a "${fulldomain}")" |
|||
fi |
|||
|
|||
_debug fulldomain "${fulldomain}" |
|||
_debug fulldomain_idn "${fulldomain_idn}" |
|||
|
|||
txtvalue="${2}" |
|||
_debug txtvalue "${txtvalue}" |
|||
|
|||
# This header is required for curl calls. |
|||
_H1="X-Requested-With: XMLHttpRequest" |
|||
export _H1 |
|||
} |
|||
|
|||
_cyon_print_header() { |
|||
if [ "${1}" = "add" ]; then |
|||
_info "" |
|||
_info "+---------------------------------------------+" |
|||
_info "| Adding DNS TXT entry to your cyon.ch domain |" |
|||
_info "+---------------------------------------------+" |
|||
_info "" |
|||
_info " * Full Domain: ${fulldomain}" |
|||
_info " * TXT Value: ${txtvalue}" |
|||
_info "" |
|||
elif [ "${1}" = "delete" ]; then |
|||
_info "" |
|||
_info "+-------------------------------------------------+" |
|||
_info "| Deleting DNS TXT entry from your cyon.ch domain |" |
|||
_info "+-------------------------------------------------+" |
|||
_info "" |
|||
_info " * Full Domain: ${fulldomain}" |
|||
_info "" |
|||
fi |
|||
} |
|||
|
|||
_cyon_get_cookie_header() { |
|||
printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" |
|||
} |
|||
|
|||
_cyon_login() { |
|||
_info " - Logging in..." |
|||
|
|||
username_encoded="$(printf "%s" "${CY_Username}" | _url_encode)" |
|||
password_encoded="$(printf "%s" "${CY_Password}" | _url_encode)" |
|||
|
|||
login_url="https://my.cyon.ch/auth/index/dologin-async" |
|||
login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" |
|||
|
|||
login_response="$(_post "$login_data" "$login_url")" |
|||
_debug login_response "${login_response}" |
|||
|
|||
# Bail if login fails. |
|||
if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then |
|||
_err " $(printf "%s" "${login_response}" | _cyon_get_response_message)" |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
_info " success" |
|||
|
|||
# NECESSARY!! Load the main page after login, to get the new cookie. |
|||
_H2="$(_cyon_get_cookie_header)" |
|||
export _H2 |
|||
|
|||
_get "https://my.cyon.ch/" >/dev/null |
|||
|
|||
# todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. |
|||
|
|||
# 2FA authentication with OTP? |
|||
if [ ! -z "${CY_OTP_Secret}" ]; then |
|||
_info " - Authorising with OTP code..." |
|||
|
|||
if ! _exists oathtool; then |
|||
_err "Please install oathtool to use 2 Factor Authentication." |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
# Get OTP code with the defined secret. |
|||
otp_code="$(oathtool --base32 --totp "${CY_OTP_Secret}" 2>/dev/null)" |
|||
|
|||
login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" |
|||
login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0" |
|||
|
|||
login_otp_response="$(_post "$login_otp_data" "$login_otp_url")" |
|||
_debug login_otp_response "${login_otp_response}" |
|||
|
|||
# Bail if OTP authentication fails. |
|||
if [ "$(printf "%s" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then |
|||
_err " $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)" |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
_info " success" |
|||
fi |
|||
|
|||
_info "" |
|||
} |
|||
|
|||
_cyon_logout() { |
|||
_info " - Logging out..." |
|||
|
|||
_get "https://my.cyon.ch/auth/index/dologout" >/dev/null |
|||
|
|||
_info " success" |
|||
_info "" |
|||
} |
|||
|
|||
_cyon_change_domain_env() { |
|||
_info " - Changing domain environment..." |
|||
|
|||
# Get the "example.com" part of the full domain name. |
|||
domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" |
|||
_debug "Changing domain environment to ${domain_env}" |
|||
|
|||
gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" |
|||
_debug gloo_item_key "${gloo_item_key}" |
|||
|
|||
domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}" |
|||
|
|||
domain_env_response="$(_get "${domain_env_url}")" |
|||
_debug domain_env_response "${domain_env_response}" |
|||
|
|||
if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi |
|||
|
|||
domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" |
|||
|
|||
# Bail if domain environment change fails. |
|||
if [ "${domain_env_success}" != "true" ]; then |
|||
_err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
_info " success" |
|||
_info "" |
|||
} |
|||
|
|||
_cyon_add_txt() { |
|||
_info " - Adding DNS TXT entry..." |
|||
|
|||
add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" |
|||
add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" |
|||
|
|||
add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" |
|||
_debug add_txt_response "${add_txt_response}" |
|||
|
|||
if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi |
|||
|
|||
add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" |
|||
add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" |
|||
|
|||
# Bail if adding TXT entry fails. |
|||
if [ "${add_txt_status}" != "true" ]; then |
|||
_err " ${add_txt_message}" |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
|
|||
_info " success (TXT|${fulldomain_idn}.|${txtvalue})" |
|||
_info "" |
|||
} |
|||
|
|||
_cyon_delete_txt() { |
|||
_info " - Deleting DNS TXT entry..." |
|||
|
|||
list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" |
|||
|
|||
list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" |
|||
_debug list_txt_response "${list_txt_response}" |
|||
|
|||
if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi |
|||
|
|||
# Find and delete all acme challenge entries for the $fulldomain. |
|||
_dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" |
|||
|
|||
printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do |
|||
dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" |
|||
dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" |
|||
|
|||
if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then |
|||
continue |
|||
fi |
|||
|
|||
hash_encoded="$(printf "%s" "${_hash}" | _url_encode)" |
|||
identifier_encoded="$(printf "%s" "${_identifier}" | _url_encode)" |
|||
|
|||
delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" |
|||
delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" |
|||
|
|||
delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")" |
|||
_debug delete_txt_response "${delete_txt_response}" |
|||
|
|||
if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi |
|||
|
|||
delete_txt_message="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_message)" |
|||
delete_txt_status="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_status)" |
|||
|
|||
# Skip if deleting TXT entry fails. |
|||
if [ "${delete_txt_status}" != "true" ]; then |
|||
_err " ${delete_txt_message} (${_identifier})" |
|||
else |
|||
_info " success (${_identifier})" |
|||
fi |
|||
done |
|||
|
|||
_info " done" |
|||
_info "" |
|||
} |
|||
|
|||
_cyon_get_response_message() { |
|||
_egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"' |
|||
} |
|||
|
|||
_cyon_get_response_status() { |
|||
_egrep_o '"status":\w*' | cut -d : -f 2 |
|||
} |
|||
|
|||
_cyon_get_response_success() { |
|||
_egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' |
|||
} |
|||
|
|||
_cyon_check_if_2fa_missed() { |
|||
# Did we miss the 2FA? |
|||
if test "${1#*multi_factor_form}" != "${1}"; then |
|||
_err " Missed OTP authentication!" |
|||
_err "" |
|||
return 1 |
|||
fi |
|||
} |
@ -0,0 +1,184 @@ |
|||
#!/usr/bin/env sh |
|||
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- |
|||
# vim: et ts=2 sw=2 |
|||
# |
|||
# DirectAdmin 1.41.0 API |
|||
# The DirectAdmin interface has it's own Let's encrypt functionality, but this |
|||
# script can be used to generate certificates for names which are not hosted on |
|||
# DirectAdmin |
|||
# |
|||
# User must provide login data and URL to DirectAdmin incl. port. |
|||
# You can create login key, by using the Login Keys function |
|||
# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to |
|||
# - CMD_API_DNS_CONTROL |
|||
# - CMD_API_SHOW_DOMAINS |
|||
# |
|||
# See also https://www.directadmin.com/api.php and |
|||
# https://www.directadmin.com/features.php?id=1298 |
|||
# |
|||
# Report bugs to https://github.com/TigerP/acme.sh/issues |
|||
# |
|||
# Values to export: |
|||
# export DA_Api="https://remoteUser:remotePassword@da.example.com:8443" |
|||
# export DA_Api_Insecure=1 |
|||
# |
|||
# Set DA_Api_Insecure to 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.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
# Used to add txt record |
|||
dns_da_add() { |
|||
fulldomain="${1}" |
|||
txtvalue="${2}" |
|||
_debug "Calling: dns_da_add() '${fulldomain}' '${txtvalue}'" |
|||
_DA_credentials && _DA_getDomainInfo && _DA_addTxt |
|||
} |
|||
|
|||
# Usage: dns_da_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
# Used to remove the txt record after validation |
|||
dns_da_rm() { |
|||
fulldomain="${1}" |
|||
txtvalue="${2}" |
|||
_debug "Calling: dns_da_rm() '${fulldomain}' '${txtvalue}'" |
|||
_DA_credentials && _DA_getDomainInfo && _DA_rmTxt |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
# Usage: _DA_credentials |
|||
# It will check if the needed settings are available |
|||
_DA_credentials() { |
|||
DA_Api="${DA_Api:-$(_readaccountconf_mutable DA_Api)}" |
|||
DA_Api_Insecure="${DA_Api_Insecure:-$(_readaccountconf_mutable DA_Api_Insecure)}" |
|||
if [ -z "${DA_Api}" ] || [ -z "${DA_Api_Insecure}" ]; then |
|||
DA_Api="" |
|||
DA_Api_Insecure="" |
|||
_err "You haven't specified the DirectAdmin Login data, URL and whether you want check the DirectAdmin SSL cert. Please try again." |
|||
return 1 |
|||
else |
|||
_saveaccountconf_mutable DA_Api "${DA_Api}" |
|||
_saveaccountconf_mutable DA_Api_Insecure "${DA_Api_Insecure}" |
|||
# Set whether curl should use secure or insecure mode |
|||
export HTTPS_INSECURE="${DA_Api_Insecure}" |
|||
fi |
|||
} |
|||
|
|||
# Usage: _get_root _acme-challenge.www.example.com |
|||
# Split the full domain to a domain and subdomain |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=example.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
# Get a list of all the domains |
|||
# response will contain "list[]=example.com&list[]=example.org" |
|||
_da_api CMD_API_SHOW_DOMAINS "" "${domain}" |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
# not valid |
|||
_debug "The given domain $h is not valid" |
|||
return 1 |
|||
fi |
|||
if _contains "$response" "$h" >/dev/null; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
_debug "Stop on 100" |
|||
return 1 |
|||
} |
|||
|
|||
# Usage: _da_api CMD_API_* data example.com |
|||
# Use the DirectAdmin API and check the result |
|||
# returns |
|||
# response="error=0&text=Result text&details=" |
|||
_da_api() { |
|||
cmd=$1 |
|||
data=$2 |
|||
domain=$3 |
|||
_debug "$domain; $data" |
|||
response="$(_post "$data" "$DA_Api/$cmd" "" "POST")" |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $cmd" |
|||
return 1 |
|||
fi |
|||
_debug response "$response" |
|||
|
|||
case "${cmd}" in |
|||
CMD_API_DNS_CONTROL) |
|||
# Parse the result in general |
|||
# error=0&text=Records Deleted&details= |
|||
# error=1&text=Cannot View Dns Record&details=No domain provided |
|||
err_field="$(_getfield "$response" 1 '&')" |
|||
txt_field="$(_getfield "$response" 2 '&')" |
|||
details_field="$(_getfield "$response" 3 '&')" |
|||
error="$(_getfield "$err_field" 2 '=')" |
|||
text="$(_getfield "$txt_field" 2 '=')" |
|||
details="$(_getfield "$details_field" 2 '=')" |
|||
_debug "error: ${error}, text: ${text}, details: ${details}" |
|||
if [ "$error" != "0" ]; then |
|||
_err "error $response" |
|||
return 1 |
|||
fi |
|||
;; |
|||
CMD_API_SHOW_DOMAINS) ;; |
|||
esac |
|||
return 0 |
|||
} |
|||
|
|||
# Usage: _DA_getDomainInfo |
|||
# Get the root zone if possible |
|||
_DA_getDomainInfo() { |
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
else |
|||
_debug "The root domain: $_domain" |
|||
_debug "The sub domain: $_sub_domain" |
|||
fi |
|||
return 0 |
|||
} |
|||
|
|||
# Usage: _DA_addTxt |
|||
# Use the API to add a record |
|||
_DA_addTxt() { |
|||
curData="domain=${_domain}&action=add&type=TXT&name=${_sub_domain}&value=\"${txtvalue}\"" |
|||
_debug "Calling _DA_addTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'" |
|||
_da_api CMD_API_DNS_CONTROL "${curData}" "${_domain}" |
|||
_debug "Result of _DA_addTxt: '$response'" |
|||
if _contains "${response}" 'error=0'; then |
|||
_debug "Add TXT succeeded" |
|||
return 0 |
|||
fi |
|||
_debug "Add TXT failed" |
|||
return 1 |
|||
} |
|||
|
|||
# Usage: _DA_rmTxt |
|||
# Use the API to remove a record |
|||
_DA_rmTxt() { |
|||
curData="domain=${_domain}&action=select&txtrecs0=name=${_sub_domain}&value=\"${txtvalue}\"" |
|||
_debug "Calling _DA_rmTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'" |
|||
if _da_api CMD_API_DNS_CONTROL "${curData}" "${_domain}"; then |
|||
_debug "Result of _DA_rmTxt: '$response'" |
|||
else |
|||
_err "Result of _DA_rmTxt: '$response'" |
|||
fi |
|||
if _contains "${response}" 'error=0'; then |
|||
_debug "RM TXT succeeded" |
|||
return 0 |
|||
fi |
|||
_debug "RM TXT failed" |
|||
return 1 |
|||
} |
@ -0,0 +1,205 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
## Will be called by acme.sh to add the txt record to your api system. |
|||
## returns 0 means success, otherwise error. |
|||
|
|||
## Author: thewer <github at thewer.com> |
|||
## GitHub: https://github.com/gitwer/acme.sh |
|||
|
|||
## |
|||
## Environment Variables Required: |
|||
## |
|||
## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" |
|||
## |
|||
|
|||
##################### Public functions ##################### |
|||
|
|||
## Create the text record for validation. |
|||
## Usage: fulldomain txtvalue |
|||
## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" |
|||
dns_dgon_add() { |
|||
fulldomain="$(echo "$1" | _lower_case)" |
|||
txtvalue=$2 |
|||
_info "Using digitalocean dns validation - add record" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
## save the env vars (key and domain split location) for later automated use |
|||
_saveaccountconf DO_API_KEY "$DO_API_KEY" |
|||
|
|||
## split the domain for DO API |
|||
if ! _get_base_domain "$fulldomain"; then |
|||
_err "domain not found in your account for addition" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
## Set the header with our post type and key auth key |
|||
export _H1="Content-Type: application/json" |
|||
export _H2="Authorization: Bearer $DO_API_KEY" |
|||
PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records' |
|||
PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}' |
|||
|
|||
_debug PURL "$PURL" |
|||
_debug PBODY "$PBODY" |
|||
|
|||
## the create request - post |
|||
## args: BODY, URL, [need64, httpmethod] |
|||
response="$(_post "$PBODY" "$PURL")" |
|||
|
|||
## check response |
|||
if [ "$?" != "0" ]; then |
|||
_err "error in response: $response" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
|
|||
## finished correctly |
|||
return 0 |
|||
} |
|||
|
|||
## Remove the txt record after validation. |
|||
## Usage: fulldomain txtvalue |
|||
## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" |
|||
dns_dgon_rm() { |
|||
fulldomain="$(echo "$1" | _lower_case)" |
|||
txtvalue=$2 |
|||
_info "Using digitalocean dns validation - remove record" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
## split the domain for DO API |
|||
if ! _get_base_domain "$fulldomain"; then |
|||
_err "domain not found in your account for removal" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
## Set the header with our post type and key auth key |
|||
export _H1="Content-Type: application/json" |
|||
export _H2="Authorization: Bearer $DO_API_KEY" |
|||
## get URL for the list of domains |
|||
## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} |
|||
GURL="https://api.digitalocean.com/v2/domains/$_domain/records" |
|||
|
|||
## while we dont have a record ID we keep going |
|||
while [ -z "$record" ]; do |
|||
## 1) get the URL |
|||
## the create request - get |
|||
## args: URL, [onlyheader, timeout] |
|||
domain_list="$(_get "$GURL")" |
|||
## 2) find record |
|||
## check for what we are looing for: "type":"A","name":"$_sub_domain" |
|||
record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" |
|||
## 3) check record and get next page |
|||
if [ -z "$record" ]; then |
|||
## find the next page if we dont have a match |
|||
nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")" |
|||
if [ -z "$nextpage" ]; then |
|||
_err "no record and no nextpage in digital ocean DNS removal" |
|||
return 1 |
|||
fi |
|||
_debug2 nextpage "$nextpage" |
|||
GURL="$nextpage" |
|||
fi |
|||
## we break out of the loop when we have a record |
|||
done |
|||
|
|||
## we found the record |
|||
rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")" |
|||
_debug rec_id "$rec_id" |
|||
|
|||
## delete the record |
|||
## delete URL for removing the one we dont want |
|||
DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" |
|||
|
|||
## the create request - delete |
|||
## args: BODY, URL, [need64, httpmethod] |
|||
response="$(_post "" "$DURL" "" "DELETE")" |
|||
|
|||
## check response (sort of) |
|||
if [ "$?" != "0" ]; then |
|||
_err "error in remove response: $response" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
|
|||
## finished correctly |
|||
return 0 |
|||
} |
|||
|
|||
##################### Private functions below ##################### |
|||
|
|||
## Split the domain provided into the "bade domain" and the "start prefix". |
|||
## This function searches for the longest subdomain in your account |
|||
## for the full domain given and splits it into the base domain (zone) |
|||
## and the prefix/record to be added/removed |
|||
## USAGE: fulldomain |
|||
## EG: "_acme-challenge.two.three.four.domain.com" |
|||
## returns |
|||
## _sub_domain="_acme-challenge.two" |
|||
## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists |
|||
## if only "domain.com" exists it will return |
|||
## _sub_domain="_acme-challenge.two.three.four" |
|||
## _domain="domain.com" |
|||
_get_base_domain() { |
|||
# args |
|||
fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')" |
|||
_debug fulldomain "$fulldomain" |
|||
|
|||
# domain max legal length = 253 |
|||
MAX_DOM=255 |
|||
|
|||
## get a list of domains for the account to check thru |
|||
## Set the headers |
|||
export _H1="Content-Type: application/json" |
|||
export _H2="Authorization: Bearer $DO_API_KEY" |
|||
_debug DO_API_KEY "$DO_API_KEY" |
|||
## get URL for the list of domains |
|||
## havent seen this request paginated, tested with 18 domains (more requires manual requests with DO) |
|||
DOMURL="https://api.digitalocean.com/v2/domains" |
|||
|
|||
## get the domain list (DO gives basically a full XFER!) |
|||
domain_list="$(_get "$DOMURL")" |
|||
|
|||
## check response |
|||
if [ "$?" != "0" ]; then |
|||
_err "error in domain_list response: $domain_list" |
|||
return 1 |
|||
fi |
|||
_debug2 domain_list "$domain_list" |
|||
|
|||
## for each shortening of our $fulldomain, check if it exists in the $domain_list |
|||
## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" |
|||
i=2 |
|||
while [ $i -gt 0 ]; do |
|||
## get next longest domain |
|||
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") |
|||
## check we got something back from our cut (or are we at the end) |
|||
if [ -z "$_domain" ]; then |
|||
## we got to the end of the domain - invalid domain |
|||
_err "domain not found in DigitalOcean account" |
|||
return 1 |
|||
fi |
|||
## we got part of a domain back - grep it out |
|||
found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" |
|||
## check if it exists |
|||
if [ ! -z "$found" ]; then |
|||
## exists - exit loop returning the parts |
|||
sub_point=$(_math $i - 1) |
|||
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") |
|||
_debug _domain "$_domain" |
|||
_debug _sub_domain "$_sub_domain" |
|||
return 0 |
|||
fi |
|||
## increment cut point $i |
|||
i=$(_math $i + 1) |
|||
done |
|||
|
|||
## we went through the entire domain zone list and dint find one that matched |
|||
## doesnt look like we can add in the record |
|||
_err "domain not found in DigitalOcean account, but we should never get here" |
|||
return 1 |
|||
} |
@ -0,0 +1,215 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# DNSimple domain api |
|||
# https://github.com/pho3nixf1re/acme.sh/issues |
|||
# |
|||
# This is your oauth token which can be acquired on the account page. Please |
|||
# note that this must be an _account_ token and not a _user_ token. |
|||
# https://dnsimple.com/a/<your account id>/account/access_tokens |
|||
# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
|
|||
DNSimple_API="https://api.dnsimple.com/v2" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_dnsimple_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$DNSimple_OAUTH_TOKEN" ]; then |
|||
DNSimple_OAUTH_TOKEN="" |
|||
_err "You have not set the dnsimple oauth token yet." |
|||
_err "Please visit https://dnsimple.com/user to generate it." |
|||
return 1 |
|||
fi |
|||
|
|||
# save the oauth token for later |
|||
_saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" |
|||
|
|||
if ! _get_account_id; then |
|||
_err "failed to retrive account id" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_get_records "$_account_id" "$_domain" "$_sub_domain" |
|||
|
|||
if [ "$_records_count" = "0" ]; then |
|||
_info "Adding record" |
|||
if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |
|||
if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then |
|||
_info "Added" |
|||
return 0 |
|||
else |
|||
_err "Unexpected response while adding text record." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
else |
|||
_info "Updating record" |
|||
_extract_record_id "$_records" "$_sub_domain" |
|||
|
|||
if _dnsimple_rest \ |
|||
PATCH \ |
|||
"$_account_id/zones/$_domain/records/$_record_id" \ |
|||
"{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |
|||
|
|||
_info "Updated!" |
|||
return 0 |
|||
fi |
|||
|
|||
_err "Update error" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
# fulldomain |
|||
dns_dnsimple_rm() { |
|||
fulldomain=$1 |
|||
|
|||
if ! _get_account_id; then |
|||
_err "failed to retrive account id" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_get_records "$_account_id" "$_domain" "$_sub_domain" |
|||
_extract_record_id "$_records" "$_sub_domain" |
|||
|
|||
if [ "$_record_id" ]; then |
|||
|
|||
if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then |
|||
_info "removed record" "$_record_id" |
|||
return 0 |
|||
fi |
|||
fi |
|||
|
|||
_err "failed to remove record" "$_record_id" |
|||
return 1 |
|||
|
|||
} |
|||
|
|||
#################### Private functions bellow ################################## |
|||
# _acme-challenge.www.domain.com |
|||
# returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
previous=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
# not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dnsimple_rest GET "$_account_id/zones/$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" 'not found'; then |
|||
_debug "$h not found" |
|||
else |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous) |
|||
_domain="$h" |
|||
|
|||
_debug _domain "$_domain" |
|||
_debug _sub_domain "$_sub_domain" |
|||
|
|||
return 0 |
|||
fi |
|||
|
|||
previous="$i" |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
# returns _account_id |
|||
_get_account_id() { |
|||
_debug "retrive account id" |
|||
if ! _dnsimple_rest GET "whoami"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"account\":null"; then |
|||
_err "no account associated with this token" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "timeout"; then |
|||
_err "timeout retrieving account id" |
|||
return 1 |
|||
fi |
|||
|
|||
_account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1) |
|||
_debug _account_id "$_account_id" |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
# returns |
|||
# _records |
|||
# _records_count |
|||
_get_records() { |
|||
account_id=$1 |
|||
domain=$2 |
|||
sub_domain=$3 |
|||
|
|||
_debug "fetching txt records" |
|||
_dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100" |
|||
|
|||
if ! _contains "$response" "\"id\":"; then |
|||
_err "failed to retrieve records" |
|||
return 1 |
|||
fi |
|||
|
|||
_records_count=$(printf "%s" "$response" | _egrep_o "\"name\":\"$sub_domain\"" | wc -l | _egrep_o "[0-9]+") |
|||
_records=$response |
|||
_debug _records_count "$_records_count" |
|||
} |
|||
|
|||
# returns _record_id |
|||
_extract_record_id() { |
|||
_record_id=$(printf "%s" "$_records" | _egrep_o "\"id\":[^,]*,\"zone_id\":\"[^,]*\",\"parent_id\":null,\"name\":\"$_sub_domain\"" | cut -d: -f2 | cut -d, -f1) |
|||
_debug "_record_id" "$_record_id" |
|||
} |
|||
|
|||
# returns response |
|||
_dnsimple_rest() { |
|||
method=$1 |
|||
path="$2" |
|||
data="$3" |
|||
request_url="$DNSimple_API/$path" |
|||
_debug "$path" |
|||
|
|||
export _H1="Accept: application/json" |
|||
export _H2="Authorization: Bearer $DNSimple_OAUTH_TOKEN" |
|||
|
|||
if [ "$data" ] || [ "$method" = "DELETE" ]; then |
|||
_H1="Content-Type: application/json" |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$request_url" "" "$method")" |
|||
else |
|||
response="$(_get "$request_url" "" "" "$method")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $request_url" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,148 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# DNS API for Domain-Offensive / Resellerinterface / Domainrobot |
|||
|
|||
# Report bugs at https://github.com/seidler2547/acme.sh/issues |
|||
|
|||
# set these environment variables to match your customer ID and password: |
|||
# DO_PID="KD-1234567" |
|||
# DO_PW="cdfkjl3n2" |
|||
|
|||
DO_URL="https://soap.resellerinterface.de/" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_do_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
if _dns_do_authenticate; then |
|||
_info "Adding TXT record to ${_domain} as ${fulldomain}" |
|||
_dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300 |
|||
if _contains "${response}" '>success<'; then |
|||
return 0 |
|||
fi |
|||
_err "Could not create resource record, check logs" |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
#fulldomain |
|||
dns_do_rm() { |
|||
fulldomain=$1 |
|||
if _dns_do_authenticate; then |
|||
if _dns_do_list_rrs; then |
|||
_dns_do_had_error=0 |
|||
for _rrid in ${_rr_list}; do |
|||
_info "Deleting resource record $_rrid for $_domain" |
|||
_dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}" |
|||
if ! _contains "${response}" '>success<'; then |
|||
_dns_do_had_error=1 |
|||
_err "Could not delete resource record for ${_domain}, id ${_rrid}" |
|||
fi |
|||
done |
|||
return $_dns_do_had_error |
|||
fi |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
_dns_do_authenticate() { |
|||
_info "Authenticating as ${DO_PID}" |
|||
_dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}" |
|||
if _contains "${response}" '>success<'; then |
|||
_get_root "$fulldomain" |
|||
_debug "_domain $_domain" |
|||
return 0 |
|||
else |
|||
_err "Authentication failed, are DO_PID and DO_PW set correctly?" |
|||
fi |
|||
return 1 |
|||
} |
|||
|
|||
_dns_do_list_rrs() { |
|||
_dns_do_soap getRRList origin "${_domain}" |
|||
if ! _contains "${response}" 'SOAP-ENC:Array'; then |
|||
_err "getRRList origin ${_domain} failed" |
|||
return 1 |
|||
fi |
|||
_rr_list="$(echo "${response}" \ |
|||
| tr -d "\n\r\t" \ |
|||
| sed -e 's/<item xsi:type="ns2:Map">/\n/g' \ |
|||
| grep ">$(_regexcape "$fulldomain")</value>" \ |
|||
| sed -e 's/<\/item>/\n/g' \ |
|||
| grep '>id</key><value' \ |
|||
| _egrep_o '>[0-9]{1,16}<' \ |
|||
| tr -d '><')" |
|||
[ "${_rr_list}" ] |
|||
} |
|||
|
|||
_dns_do_soap() { |
|||
func="$1" |
|||
shift |
|||
# put the parameters to xml |
|||
body="<tns:${func} xmlns:tns=\"${DO_URL}\">" |
|||
while [ "$1" ]; do |
|||
_k="$1" |
|||
shift |
|||
_v="$1" |
|||
shift |
|||
body="$body<$_k>$_v</$_k>" |
|||
done |
|||
body="$body</tns:${func}>" |
|||
_debug2 "SOAP request ${body}" |
|||
|
|||
# build SOAP XML |
|||
_xml='<?xml version="1.0" encoding="UTF-8"?> |
|||
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"> |
|||
<env:Body>'"$body"'</env:Body> |
|||
</env:Envelope>' |
|||
|
|||
# set SOAP headers |
|||
export _H1="SOAPAction: ${DO_URL}#${func}" |
|||
|
|||
if ! response="$(_post "${_xml}" "${DO_URL}")"; then |
|||
_err "Error <$1>" |
|||
return 1 |
|||
fi |
|||
_debug2 "SOAP response $response" |
|||
|
|||
# retrieve cookie header |
|||
_H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)" |
|||
export _H2 |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_get_root() { |
|||
domain=$1 |
|||
i=1 |
|||
|
|||
_dns_do_soap getDomainList |
|||
_all_domains="$(echo "${response}" \ |
|||
| tr -d "\n\r\t " \ |
|||
| _egrep_o 'domain</key><value[^>]+>[^<]+' \ |
|||
| sed -e 's/^domain<\/key><value[^>]*>//g')" |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
|
|||
i=$(_math $i + 1) |
|||
done |
|||
_debug "$domain not found" |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_regexcape() { |
|||
echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g' |
|||
} |
@ -0,0 +1,161 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Dnspod.cn Domain api |
|||
# |
|||
#DP_Id="1234" |
|||
# |
|||
#DP_Key="sADDsdasdgdsf" |
|||
|
|||
REST_API="https://dnsapi.cn" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_dp_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" |
|||
DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" |
|||
if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then |
|||
DP_Id="" |
|||
DP_Key="" |
|||
_err "You don't specify dnspod api key and key id yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable DP_Id "$DP_Id" |
|||
_saveaccountconf_mutable DP_Key "$DP_Key" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
add_record "$_domain" "$_sub_domain" "$txtvalue" |
|||
|
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_dp_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" |
|||
DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" |
|||
|
|||
_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" |
|||
|
|||
} |
|||
|
|||
#add the txt record. |
|||
#usage: root sub txtvalue |
|||
add_record() { |
|||
root=$1 |
|||
sub=$2 |
|||
txtvalue=$3 |
|||
fulldomain="$sub.$root" |
|||
|
|||
_info "Adding record" |
|||
|
|||
if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then |
|||
return 1 |
|||
fi |
|||
|
|||
_contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists" |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "Action completed successful"; then |
|||
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") |
|||
_debug _domain_id "$_domain_id" |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_debug _sub_domain "$_sub_domain" |
|||
_domain="$h" |
|||
_debug _domain "$_domain" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p="$i" |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
#Usage: method URI data |
|||
_rest() { |
|||
m="$1" |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
url="$REST_API/$ep" |
|||
|
|||
_debug url "$url" |
|||
|
|||
if [ "$m" = "GET" ]; then |
|||
response="$(_get "$url" | tr -d '\r')" |
|||
else |
|||
_debug2 data "$data" |
|||
response="$(_post "$data" "$url" | tr -d '\r')" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,97 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Author: RhinoLance |
|||
#Report Bugs here: https://github.com/RhinoLance/acme.sh |
|||
# |
|||
|
|||
#define the api endpoint |
|||
DH_API_ENDPOINT="https://api.dreamhost.com/" |
|||
querystring="" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_dreamhost_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! validate "$fulldomain" "$txtvalue"; then |
|||
return 1 |
|||
fi |
|||
|
|||
querystring="key=$DH_API_KEY&cmd=dns-add_record&record=$fulldomain&type=TXT&value=$txtvalue" |
|||
if ! submit "$querystring"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_dreamhost_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! validate "$fulldomain" "$txtvalue"; then |
|||
return 1 |
|||
fi |
|||
|
|||
querystring="key=$DH_API_KEY&cmd=dns-remove_record&record=$fulldomain&type=TXT&value=$txtvalue" |
|||
if ! submit "$querystring"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
#send the command to the api endpoint. |
|||
submit() { |
|||
querystring=$1 |
|||
|
|||
url="$DH_API_ENDPOINT?$querystring" |
|||
|
|||
_debug url "$url" |
|||
|
|||
if ! response="$(_get "$url")"; then |
|||
_err "Error <$1>" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$2" ]; then |
|||
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" |
|||
if [ -n "$message" ]; then |
|||
_err "$message" |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
_debug response "$response" |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#check that we have a valid API Key |
|||
validate() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
_info "Using dreamhost" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
#retrieve the API key from the environment variable if it exists, otherwise look for a saved key. |
|||
DH_API_KEY="${DH_API_KEY:-$(_readaccountconf_mutable DH_API_KEY)}" |
|||
|
|||
if [ -z "$DH_API_KEY" ]; then |
|||
DH_API_KEY="" |
|||
_err "You didn't specify the DreamHost api key yet (export DH_API_KEY=\"<api key>\")" |
|||
_err "Please login to your control panel, create a key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key to the account conf file. |
|||
_saveaccountconf_mutable DH_API_KEY "$DH_API_KEY" |
|||
} |
@ -0,0 +1,128 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Created by RaidenII, to use DuckDNS's API to add/remove text records |
|||
#06/27/2017 |
|||
|
|||
# Pass credentials before "acme.sh --issue --dns dns_duckdns ..." |
|||
# -- |
|||
# export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" |
|||
# -- |
|||
# |
|||
# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure may need to be used with acme.sh |
|||
|
|||
DuckDNS_API="https://www.duckdns.org/update" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_duckdns_add _acme-challenge.domain.duckdns.org "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_duckdns_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}" |
|||
if [ -z "$DuckDNS_Token" ]; then |
|||
_err "You must export variable: DuckDNS_Token" |
|||
_err "The token for your DuckDNS account is necessary." |
|||
_err "You can look it up in your DuckDNS account." |
|||
return 1 |
|||
fi |
|||
|
|||
# Now save the credentials. |
|||
_saveaccountconf_mutable DuckDNS_Token "$DuckDNS_Token" |
|||
|
|||
# Unfortunately, DuckDNS does not seems to support lookup domain through API |
|||
# So I assume your credentials (which are your domain and token) are correct |
|||
# If something goes wrong, we will get a KO response from DuckDNS |
|||
|
|||
if ! _duckdns_get_domain; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Now add the TXT record to DuckDNS |
|||
_info "Trying to add TXT record" |
|||
if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=$txtvalue"; then |
|||
if [ "$response" = "OK" ]; then |
|||
_info "TXT record has been successfully added to your DuckDNS domain." |
|||
_info "Note that all subdomains under this domain uses the same TXT record." |
|||
return 0 |
|||
else |
|||
_err "Errors happened during adding the TXT record, response=$response" |
|||
return 1 |
|||
fi |
|||
else |
|||
_err "Errors happened during adding the TXT record." |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_duckdns_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}" |
|||
if [ -z "$DuckDNS_Token" ]; then |
|||
_err "You must export variable: DuckDNS_Token" |
|||
_err "The token for your DuckDNS account is necessary." |
|||
_err "You can look it up in your DuckDNS account." |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _duckdns_get_domain; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Now remove the TXT record from DuckDNS |
|||
_info "Trying to remove TXT record" |
|||
if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=&clear=true"; then |
|||
if [ "$response" = "OK" ]; then |
|||
_info "TXT record has been successfully removed from your DuckDNS domain." |
|||
return 0 |
|||
else |
|||
_err "Errors happened during removing the TXT record, response=$response" |
|||
return 1 |
|||
fi |
|||
else |
|||
_err "Errors happened during removing the TXT record." |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
#fulldomain=_acme-challenge.domain.duckdns.org |
|||
#returns |
|||
# _duckdns_domain=domain |
|||
_duckdns_get_domain() { |
|||
|
|||
# We'll extract the domain/username from full domain |
|||
_duckdns_domain="$(printf "%s" "$fulldomain" | _lower_case | _egrep_o '[.][^.][^.]*[.]duckdns.org' | cut -d . -f 2)" |
|||
|
|||
if [ -z "$_duckdns_domain" ]; then |
|||
_err "Error extracting the domain." |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#Usage: method URI |
|||
_duckdns_rest() { |
|||
method=$1 |
|||
param="$2" |
|||
_debug param "$param" |
|||
url="$DuckDNS_API?$param" |
|||
_debug url "$url" |
|||
|
|||
# DuckDNS uses GET to update domain info |
|||
if [ "$method" = "GET" ]; then |
|||
response="$(_get "$url")" |
|||
else |
|||
_err "Unsupported method" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,339 @@ |
|||
#!/usr/bin/env sh |
|||
# |
|||
# Dyn.com Domain API |
|||
# |
|||
# Author: Gerd Naschenweng |
|||
# https://github.com/magicdude4eva |
|||
# |
|||
# Dyn Managed DNS API |
|||
# https://help.dyn.com/dns-api-knowledge-base/ |
|||
# |
|||
# It is recommended to add a "Dyn Managed DNS" user specific for API access. |
|||
# The "Zones & Records Permissions" required by this script are: |
|||
# -- |
|||
# RecordAdd |
|||
# RecordUpdate |
|||
# RecordDelete |
|||
# RecordGet |
|||
# ZoneGet |
|||
# ZoneAddNode |
|||
# ZoneRemoveNode |
|||
# ZonePublish |
|||
# -- |
|||
# |
|||
# Pass credentials before "acme.sh --issue --dns dns_dyn ..." |
|||
# -- |
|||
# export DYN_Customer="customer" |
|||
# export DYN_Username="apiuser" |
|||
# export DYN_Password="secret" |
|||
# -- |
|||
|
|||
DYN_API="https://api.dynect.net/REST" |
|||
|
|||
#REST_API |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "Challenge-code" |
|||
dns_dyn_add() { |
|||
fulldomain="$1" |
|||
txtvalue="$2" |
|||
|
|||
DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}" |
|||
DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}" |
|||
DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}" |
|||
if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then |
|||
DYN_Customer="" |
|||
DYN_Username="" |
|||
DYN_Password="" |
|||
_err "You must export variables: DYN_Customer, DYN_Username and DYN_Password" |
|||
return 1 |
|||
fi |
|||
|
|||
#save the config variables to the account conf file. |
|||
_saveaccountconf_mutable DYN_Customer "$DYN_Customer" |
|||
_saveaccountconf_mutable DYN_Username "$DYN_Username" |
|||
_saveaccountconf_mutable DYN_Password "$DYN_Password" |
|||
|
|||
if ! _dyn_get_authtoken; then |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$_dyn_authtoken" ]; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_get_zone; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_add_record; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_publish_zone; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
_dyn_end_session |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_dyn_rm() { |
|||
fulldomain="$1" |
|||
txtvalue="$2" |
|||
|
|||
DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}" |
|||
DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}" |
|||
DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}" |
|||
if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then |
|||
DYN_Customer="" |
|||
DYN_Username="" |
|||
DYN_Password="" |
|||
_err "You must export variables: DYN_Customer, DYN_Username and DYN_Password" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_get_authtoken; then |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$_dyn_authtoken" ]; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_get_zone; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_get_record_id; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$_dyn_record_id" ]; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_rm_record; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dyn_publish_zone; then |
|||
_dyn_end_session |
|||
return 1 |
|||
fi |
|||
|
|||
_dyn_end_session |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
#get Auth-Token |
|||
_dyn_get_authtoken() { |
|||
|
|||
_info "Start Dyn API Session" |
|||
|
|||
data="{\"customer_name\":\"$DYN_Customer\", \"user_name\":\"$DYN_Username\", \"password\":\"$DYN_Password\"}" |
|||
dyn_url="$DYN_API/Session/" |
|||
method="POST" |
|||
|
|||
_debug data "$data" |
|||
_debug dyn_url "$dyn_url" |
|||
|
|||
export _H1="Content-Type: application/json" |
|||
|
|||
response="$(_post "$data" "$dyn_url" "" "$method")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_dyn_authtoken="$(printf "%s\n" "$response" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" |
|||
_info "Token received" |
|||
_debug _dyn_authtoken "$_dyn_authtoken" |
|||
return 0 |
|||
fi |
|||
|
|||
_dyn_authtoken="" |
|||
_err "get token failed" |
|||
return 1 |
|||
} |
|||
|
|||
#fulldomain=_acme-challenge.www.domain.com |
|||
#returns |
|||
# _dyn_zone=domain.com |
|||
_dyn_get_zone() { |
|||
i=2 |
|||
while true; do |
|||
domain="$(printf "%s" "$fulldomain" | cut -d . -f "$i-100")" |
|||
if [ -z "$domain" ]; then |
|||
break |
|||
fi |
|||
|
|||
dyn_url="$DYN_API/Zone/$domain/" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_get "$dyn_url" "" "")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug dyn_url "$dyn_url" |
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_dyn_zone="$domain" |
|||
return 0 |
|||
fi |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
|
|||
_dyn_zone="" |
|||
_err "get zone failed" |
|||
return 1 |
|||
} |
|||
|
|||
#add TXT record |
|||
_dyn_add_record() { |
|||
|
|||
_info "Adding TXT record" |
|||
|
|||
data="{\"rdata\":{\"txtdata\":\"$txtvalue\"},\"ttl\":\"300\"}" |
|||
dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/" |
|||
method="POST" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_post "$data" "$dyn_url" "" "$method")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_info "TXT Record successfully added" |
|||
return 0 |
|||
fi |
|||
|
|||
_err "add TXT record failed" |
|||
return 1 |
|||
} |
|||
|
|||
#publish the zone |
|||
_dyn_publish_zone() { |
|||
|
|||
_info "Publishing zone" |
|||
|
|||
data="{\"publish\":\"true\"}" |
|||
dyn_url="$DYN_API/Zone/$_dyn_zone/" |
|||
method="PUT" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_post "$data" "$dyn_url" "" "$method")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_info "Zone published" |
|||
return 0 |
|||
fi |
|||
|
|||
_err "publish zone failed" |
|||
return 1 |
|||
} |
|||
|
|||
#get record_id of TXT record so we can delete the record |
|||
_dyn_get_record_id() { |
|||
|
|||
_info "Getting record_id of TXT record" |
|||
|
|||
dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_get "$dyn_url" "" "")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_dyn_record_id="$(printf "%s\n" "$response" | _egrep_o "\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/[^\"]*" | _head_n 1 | sed "s#^\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/##")" |
|||
_debug _dyn_record_id "$_dyn_record_id" |
|||
return 0 |
|||
fi |
|||
|
|||
_dyn_record_id="" |
|||
_err "getting record_id failed" |
|||
return 1 |
|||
} |
|||
|
|||
#delete TXT record |
|||
_dyn_rm_record() { |
|||
|
|||
_info "Deleting TXT record" |
|||
|
|||
dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/$_dyn_record_id/" |
|||
method="DELETE" |
|||
|
|||
_debug dyn_url "$dyn_url" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_post "" "$dyn_url" "" "$method")" |
|||
sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" |
|||
|
|||
_debug response "$response" |
|||
_debug sessionstatus "$sessionstatus" |
|||
|
|||
if [ "$sessionstatus" = "success" ]; then |
|||
_info "TXT record successfully deleted" |
|||
return 0 |
|||
fi |
|||
|
|||
_err "delete TXT record failed" |
|||
return 1 |
|||
} |
|||
|
|||
#logout |
|||
_dyn_end_session() { |
|||
|
|||
_info "End Dyn API Session" |
|||
|
|||
dyn_url="$DYN_API/Session/" |
|||
method="DELETE" |
|||
|
|||
_debug dyn_url "$dyn_url" |
|||
|
|||
export _H1="Auth-Token: $_dyn_authtoken" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_post "" "$dyn_url" "" "$method")" |
|||
|
|||
_debug response "$response" |
|||
|
|||
_dyn_authtoken="" |
|||
return 0 |
|||
} |
@ -0,0 +1,228 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Client ID |
|||
#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d" |
|||
# |
|||
#Secret |
|||
#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9" |
|||
# |
|||
#Token |
|||
Dynu_Token="" |
|||
# |
|||
#Endpoint |
|||
Dynu_EndPoint="https://api.dynu.com/v1" |
|||
# |
|||
#Author: Dynu Systems, Inc. |
|||
#Report Bugs here: https://github.com/shar0119/acme.sh |
|||
# |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_dynu_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then |
|||
Dynu_ClientId="" |
|||
Dynu_Secret="" |
|||
_err "Dynu client id and secret is not specified." |
|||
_err "Please create you API client id and secret and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the client id and secret to the account conf file. |
|||
_saveaccountconf Dynu_ClientId "$Dynu_ClientId" |
|||
_saveaccountconf Dynu_Secret "$Dynu_Secret" |
|||
|
|||
if [ -z "$Dynu_Token" ]; then |
|||
_info "Getting Dynu token." |
|||
if ! _dynu_authentication; then |
|||
_err "Can not get token." |
|||
fi |
|||
fi |
|||
|
|||
_debug "Detect root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Invalid domain." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _node "$_node" |
|||
_debug _domain_name "$_domain_name" |
|||
|
|||
_info "Creating TXT record." |
|||
if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "text_data"; then |
|||
_err "Could not add TXT record." |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_dynu_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then |
|||
Dynu_ClientId="" |
|||
Dynu_Secret="" |
|||
_err "Dynu client id and secret is not specified." |
|||
_err "Please create you API client id and secret and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the client id and secret to the account conf file. |
|||
_saveaccountconf Dynu_ClientId "$Dynu_ClientId" |
|||
_saveaccountconf Dynu_Secret "$Dynu_Secret" |
|||
|
|||
if [ -z "$Dynu_Token" ]; then |
|||
_info "Getting Dynu token." |
|||
if ! _dynu_authentication; then |
|||
_err "Can not get token." |
|||
fi |
|||
fi |
|||
|
|||
_debug "Detect root zone." |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Invalid domain." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _node "$_node" |
|||
_debug _domain_name "$_domain_name" |
|||
|
|||
_info "Checking for TXT record." |
|||
if ! _get_recordid "$fulldomain" "$txtvalue"; then |
|||
_err "Could not get TXT record id." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ "$_dns_record_id" = "" ]; then |
|||
_err "TXT record not found." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Removing TXT record." |
|||
if ! _delete_txt_record "$_dns_record_id"; then |
|||
_err "Could not remove TXT record $_dns_record_id." |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
######## Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _node=_acme-challenge.www |
|||
# _domain_name=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _dynu_rest GET "dns/get/$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then |
|||
_domain_name=$h |
|||
_node=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
|
|||
} |
|||
|
|||
_get_recordid() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "$txtvalue"; then |
|||
_dns_record_id=0 |
|||
return 0 |
|||
fi |
|||
|
|||
_dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2) |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_delete_txt_record() { |
|||
_dns_record_id=$1 |
|||
|
|||
if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "true"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
_dynu_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Authorization: Bearer $Dynu_Token" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
if [ "$data" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")" |
|||
else |
|||
_info "Getting $Dynu_EndPoint/$ep" |
|||
response="$(_get "$Dynu_EndPoint/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
|||
|
|||
_dynu_authentication() { |
|||
realm="$(printf "%s" "$Dynu_ClientId:$Dynu_Secret" | _base64)" |
|||
|
|||
export _H1="Authorization: Basic $realm" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
response="$(_get "$Dynu_EndPoint/oauth2/token")" |
|||
if [ "$?" != "0" ]; then |
|||
_err "Authentication failed." |
|||
return 1 |
|||
fi |
|||
if _contains "$response" "accessToken"; then |
|||
Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2) |
|||
fi |
|||
if _contains "$Dynu_Token" "null"; then |
|||
Dynu_Token="" |
|||
fi |
|||
|
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,326 @@ |
|||
#!/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 following 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")" |
|||
|
|||
_debug "top_domain: $top_domain" |
|||
_debug "sub_domain: $sub_domain" |
|||
|
|||
# Sometimes FreeDNS does not return 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 changed? If so..." |
|||
_err "Please export as FREEDNS_User / FREEDNS_Password and try again." |
|||
fi |
|||
return 1 |
|||
fi |
|||
|
|||
subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$top_domain")" |
|||
_debug3 "subdomain_csv: $subdomain_csv" |
|||
|
|||
# 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)" |
|||
i=0 |
|||
found=0 |
|||
DNSdomainid="" |
|||
while [ "$i" -lt "$lines" ]; do |
|||
i="$(_math "$i" + 1)" |
|||
line="$(echo "$subdomain_csv" | sed -n "${i}p")" |
|||
_debug2 "line: $line" |
|||
if [ $found = 0 ] && _contains "$line" "<td>$top_domain</td>"; then |
|||
# this line will contain DNSdomainid for the top_domain |
|||
DNSdomainid="$(echo "$line" | _egrep_o "edit_domain_id *= *.*>" | cut -d = -f 2 | cut -d '>' -f 1)" |
|||
_debug2 "DNSdomainid: $DNSdomainid" |
|||
found=1 |
|||
break |
|||
fi |
|||
done |
|||
|
|||
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 |
|||
_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 |
|||
|
|||
# Add in new TXT record with the value provided |
|||
_debug "Adding TXT record for $fulldomain, $txtvalue" |
|||
_freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" |
|||
return $? |
|||
} |
|||
|
|||
#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() function |
|||
FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")" |
|||
_debug "FreeDNS login cookies: $FREEDNS_COOKIE" |
|||
|
|||
# Sometimes FreeDNS does not return 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 |
|||
|
|||
subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$fulldomain")" |
|||
_debug3 "subdomain_csv: $subdomain_csv" |
|||
|
|||
# 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)" |
|||
i=0 |
|||
found=0 |
|||
DNSdataid="" |
|||
while [ "$i" -lt "$lines" ]; do |
|||
i="$(_math "$i" + 1)" |
|||
line="$(echo "$subdomain_csv" | sed -n "${i}p")" |
|||
_debug3 "line: $line" |
|||
DNSname="$(echo "$line" | _egrep_o 'edit.php.*</a>' | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
_debug2 "DNSname: $DNSname" |
|||
if [ "$DNSname" = "$fulldomain" ]; then |
|||
DNStype="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '4p' | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
_debug2 "DNStype: $DNStype" |
|||
if [ "$DNStype" = "TXT" ]; then |
|||
DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)" |
|||
_debug2 "DNSdataid: $DNSdataid" |
|||
DNSvalue="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '5p' | cut -d '>' -f 2 | cut -d '<' -f 1)" |
|||
if _startswith "$DNSvalue" """; then |
|||
# remove the quotation from the start |
|||
DNSvalue="$(echo "$DNSvalue" | cut -c 7-)" |
|||
fi |
|||
if _endswith "$DNSvalue" "..."; then |
|||
# value was truncated, remove the dot dot dot from the end |
|||
DNSvalue="$(echo "$DNSvalue" | sed 's/...$//')" |
|||
elif _endswith "$DNSvalue" """; then |
|||
# else remove the closing quotation from the end |
|||
DNSvalue="$(echo "$DNSvalue" | sed 's/......$//')" |
|||
fi |
|||
_debug2 "DNSvalue: $DNSvalue" |
|||
|
|||
if [ -n "$DNSdataid" ] && _startswith "$txtvalue" "$DNSvalue"; then |
|||
# Found a match. But note... Website is truncating the |
|||
# value field so we are only testing that part that is not |
|||
# truncated. This should be accurate enough. |
|||
_debug "Deleting TXT record for $fulldomain, $txtvalue" |
|||
_freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" |
|||
return $? |
|||
fi |
|||
|
|||
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. |
|||
_debug3 "$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 |
|||
_debug3 "htmlpage: $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 subdomain page from FreeDNS" |
|||
|
|||
htmlpage="$(_get "$url")" |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "FreeDNS retrieve subdomains failed bad RC from _get" |
|||
return 1 |
|||
elif [ -z "$htmlpage" ]; then |
|||
_err "FreeDNS returned empty subdomain page" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug3 "htmlpage: $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 |
|||
_debug3 "htmlpage: $htmlpage" |
|||
_err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" |
|||
return 1 |
|||
elif _contains "$htmlpage" "security code was incorrect"; then |
|||
_debug3 "htmlpage: $htmlpage" |
|||
_err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code" |
|||
_err "Note that you cannot use automatic DNS validation for FreeDNS public domains" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug3 "htmlpage: $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 |
|||
_debug2 "htmlheader: $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 |
|||
} |
@ -0,0 +1,123 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# Gandi LiveDNS v5 API |
|||
# http://doc.livedns.gandi.net/ |
|||
# currently under beta |
|||
# |
|||
# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable |
|||
# |
|||
#Author: Frédéric Crozat <fcrozat@suse.com> |
|||
#Report Bugs here: https://github.com/fcrozat/acme.sh |
|||
# |
|||
######## Public functions ##################### |
|||
|
|||
GANDI_LIVEDNS_API="https://dns.api.gandi.net/api/v5" |
|||
|
|||
#Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_gandi_livedns_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$GANDI_LIVEDNS_KEY" ]; then |
|||
_err "No API key specified for Gandi LiveDNS." |
|||
_err "Create your key and export it as GANDI_LIVEDNS_KEY" |
|||
return 1 |
|||
fi |
|||
|
|||
_saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
_debug domain "$_domain" |
|||
_debug sub_domain "$_sub_domain" |
|||
|
|||
_gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \ |
|||
&& _contains "$response" '{"message": "DNS Record Created"}' \ |
|||
&& _info "Add $(__green "success")" |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_gandi_livedns_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug fulldomain "$fulldomain" |
|||
_debug domain "$_domain" |
|||
_debug sub_domain "$_sub_domain" |
|||
|
|||
_gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" "" |
|||
|
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _gandi_livedns_rest GET "domains/$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" '"code": 401'; then |
|||
_err "$response" |
|||
return 1 |
|||
elif _contains "$response" '"code": 404'; then |
|||
_debug "$h not found" |
|||
else |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
p="$i" |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_gandi_livedns_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Content-Type: application/json" |
|||
export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY" |
|||
|
|||
if [ "$m" = "GET" ]; then |
|||
response="$(_get "$GANDI_LIVEDNS_API/$ep")" |
|||
else |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$GANDI_LIVEDNS_API/$ep" "" "$m")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,178 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Godaddy domain api |
|||
# |
|||
#GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#GD_Secret="asdfsdfsfsdfsdfdfsdf" |
|||
|
|||
GD_Api="https://api.godaddy.com/v1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_gd_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" |
|||
GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" |
|||
if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then |
|||
GD_Key="" |
|||
GD_Secret="" |
|||
_err "You don't specify godaddy api key and secret yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable GD_Key "$GD_Key" |
|||
_saveaccountconf_mutable GD_Secret "$GD_Secret" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug "Getting existing records" |
|||
if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "$txtvalue"; then |
|||
_info "The record is existing, skip" |
|||
return 0 |
|||
fi |
|||
|
|||
_add_data="{\"data\":\"$txtvalue\"}" |
|||
for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do |
|||
_debug2 t "$t" |
|||
if [ "$t" ]; then |
|||
_add_data="$_add_data,{\"data\":$t}" |
|||
fi |
|||
done |
|||
_debug2 _add_data "$_add_data" |
|||
|
|||
_info "Adding record" |
|||
if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then |
|||
if [ "$response" = "{}" ]; then |
|||
_info "Added, sleeping 10 seconds" |
|||
_sleep 10 |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
_err "$response" |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
|
|||
} |
|||
|
|||
#fulldomain |
|||
dns_gd_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" |
|||
GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug "Getting existing records" |
|||
if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "$txtvalue"; then |
|||
_info "The record is not existing, skip" |
|||
return 0 |
|||
fi |
|||
|
|||
_add_data="" |
|||
for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do |
|||
_debug2 t "$t" |
|||
if [ "$t" ] && [ "$t" != "\"$txtvalue\"" ]; then |
|||
if [ "$_add_data" ]; then |
|||
_add_data="$_add_data,{\"data\":$t}" |
|||
else |
|||
_add_data="{\"data\":$t}" |
|||
fi |
|||
fi |
|||
done |
|||
if [ -z "$_add_data" ]; then |
|||
_add_data="{\"data\":\"\"}" |
|||
fi |
|||
_debug2 _add_data "$_add_data" |
|||
|
|||
_gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]" |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _gd_rest GET "domains/$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" '"code":"NOT_FOUND"'; then |
|||
_debug "$h not found" |
|||
else |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
p="$i" |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_gd_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Authorization: sso-key $GD_Key:$GD_Secret" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
if [ "$data" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$GD_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$GD_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,158 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
######################################################################## |
|||
# Hurricane Electric hook script for acme.sh |
|||
# |
|||
# Environment variables: |
|||
# |
|||
# - $HE_Username (your dns.he.net username) |
|||
# - $HE_Password (your dns.he.net password) |
|||
# |
|||
# Author: Ondrej Simek <me@ondrejsimek.com> |
|||
# Git repo: https://github.com/angel333/acme.sh |
|||
|
|||
#-- dns_he_add() - Add TXT record -------------------------------------- |
|||
# Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..." |
|||
|
|||
dns_he_add() { |
|||
_full_domain=$1 |
|||
_txt_value=$2 |
|||
_info "Using DNS-01 Hurricane Electric hook" |
|||
|
|||
HE_Username="${HE_Username:-$(_readaccountconf_mutable HE_Username)}" |
|||
HE_Password="${HE_Password:-$(_readaccountconf_mutable HE_Password)}" |
|||
if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then |
|||
HE_Username= |
|||
HE_Password= |
|||
_err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables." |
|||
return 1 |
|||
fi |
|||
_saveaccountconf_mutable HE_Username "$HE_Username" |
|||
_saveaccountconf_mutable HE_Password "$HE_Password" |
|||
|
|||
# Fills in the $_zone_id |
|||
_find_zone "$_full_domain" || return 1 |
|||
_debug "Zone id \"$_zone_id\" will be used." |
|||
|
|||
body="email=${HE_Username}&pass=${HE_Password}" |
|||
body="$body&account=" |
|||
body="$body&menu=edit_zone" |
|||
body="$body&Type=TXT" |
|||
body="$body&hosted_dns_zoneid=$_zone_id" |
|||
body="$body&hosted_dns_recordid=" |
|||
body="$body&hosted_dns_editzone=1" |
|||
body="$body&Priority=" |
|||
body="$body&Name=$_full_domain" |
|||
body="$body&Content=$_txt_value" |
|||
body="$body&TTL=300" |
|||
body="$body&hosted_dns_editrecord=Submit" |
|||
response="$(_post "$body" "https://dns.he.net/")" |
|||
exit_code="$?" |
|||
if [ "$exit_code" -eq 0 ]; then |
|||
_info "TXT record added successfully." |
|||
else |
|||
_err "Couldn't add the TXT record." |
|||
fi |
|||
_debug2 response "$response" |
|||
return "$exit_code" |
|||
} |
|||
|
|||
#-- dns_he_rm() - Remove TXT record ------------------------------------ |
|||
# Usage: dns_he_rm _acme-challenge.subdomain.domain.com "XyZ123..." |
|||
|
|||
dns_he_rm() { |
|||
_full_domain=$1 |
|||
_txt_value=$2 |
|||
_info "Cleaning up after DNS-01 Hurricane Electric hook" |
|||
HE_Username="${HE_Username:-$(_readaccountconf_mutable HE_Username)}" |
|||
HE_Password="${HE_Password:-$(_readaccountconf_mutable HE_Password)}" |
|||
# fills in the $_zone_id |
|||
_find_zone "$_full_domain" || return 1 |
|||
_debug "Zone id \"$_zone_id\" will be used." |
|||
|
|||
# Find the record id to clean |
|||
body="email=${HE_Username}&pass=${HE_Password}" |
|||
body="$body&hosted_dns_zoneid=$_zone_id" |
|||
body="$body&menu=edit_zone" |
|||
body="$body&hosted_dns_editzone=" |
|||
|
|||
response="$(_post "$body" "https://dns.he.net/")" |
|||
_debug2 "response" "$response" |
|||
if ! _contains "$response" "$_txt_value"; then |
|||
_debug "The txt record is not found, just skip" |
|||
return 0 |
|||
fi |
|||
_record_id="$(echo "$response" | tr -d "#" | sed "s/<tr/#<tr/g" | tr -d "\n" | tr "#" "\n" | grep "$_full_domain" | grep '"dns_tr"' | grep "$_txt_value" | cut -d '"' -f 4)" |
|||
_debug2 _record_id "$_record_id" |
|||
if [ -z "$_record_id" ]; then |
|||
_err "Can not find record id" |
|||
return 1 |
|||
fi |
|||
# Remove the record |
|||
body="email=${HE_Username}&pass=${HE_Password}" |
|||
body="$body&menu=edit_zone" |
|||
body="$body&hosted_dns_zoneid=$_zone_id" |
|||
body="$body&hosted_dns_recordid=$_record_id" |
|||
body="$body&hosted_dns_editzone=1" |
|||
body="$body&hosted_dns_delrecord=1" |
|||
body="$body&hosted_dns_delconfirm=delete" |
|||
_post "$body" "https://dns.he.net/" \ |
|||
| grep '<div id="dns_status" onClick="hideThis(this);">Successfully removed record.</div>' \ |
|||
>/dev/null |
|||
exit_code="$?" |
|||
if [ "$exit_code" -eq 0 ]; then |
|||
_info "Record removed successfully." |
|||
else |
|||
_err "Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand." |
|||
return "$exit_code" |
|||
fi |
|||
} |
|||
|
|||
########################## PRIVATE FUNCTIONS ########################### |
|||
|
|||
_find_zone() { |
|||
_domain="$1" |
|||
body="email=${HE_Username}&pass=${HE_Password}" |
|||
response="$(_post "$body" "https://dns.he.net/")" |
|||
_debug2 response "$response" |
|||
_table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')" |
|||
_debug2 _table "$_table" |
|||
_matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')" |
|||
_debug2 _matches "$_matches" |
|||
# Zone names and zone IDs are in same order |
|||
_zone_ids=$(echo "$_matches" | _egrep_o "hosted_dns_zoneid=[0-9]*&" | cut -d = -f 2 | tr -d '&') |
|||
_zone_names=$(echo "$_matches" | _egrep_o "name=.*onclick" | cut -d '"' -f 2) |
|||
_debug2 "These are the zones on this HE account:" |
|||
_debug2 "$_zone_names" |
|||
_debug2 "And these are their respective IDs:" |
|||
_debug2 "$_zone_ids" |
|||
if [ -z "$_zone_names" ] || [ -z "$_zone_ids" ]; then |
|||
_err "Can not get zone names." |
|||
return 1 |
|||
fi |
|||
# Walk through all possible zone names |
|||
_strip_counter=1 |
|||
while true; do |
|||
_attempted_zone=$(echo "$_domain" | cut -d . -f ${_strip_counter}-) |
|||
|
|||
# All possible zone names have been tried |
|||
if [ -z "$_attempted_zone" ]; then |
|||
_err "No zone for domain \"$_domain\" found." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug "Looking for zone \"${_attempted_zone}\"" |
|||
|
|||
line_num="$(echo "$_zone_names" | grep -n "$_attempted_zone" | cut -d : -f 1)" |
|||
|
|||
if [ "$line_num" ]; then |
|||
_zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p") |
|||
_debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"." |
|||
return 0 |
|||
fi |
|||
|
|||
_debug "Zone \"$_attempted_zone\" doesn't exist, let's try a less specific zone." |
|||
_strip_counter=$(_math "$_strip_counter" + 1) |
|||
done |
|||
} |
|||
# vim: et:ts=2:sw=2: |
@ -0,0 +1,102 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
## Infoblox API integration by Jason Keller and Elijah Tenai |
|||
## |
|||
## Report any bugs via https://github.com/jasonkeller/acme.sh |
|||
|
|||
dns_infoblox_add() { |
|||
|
|||
## Nothing to see here, just some housekeeping |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View" |
|||
|
|||
_info "Using Infoblox API" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
## Check for the credentials |
|||
if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then |
|||
Infoblox_Creds="" |
|||
Infoblox_Server="" |
|||
_err "You didn't specify the credentials, server or infoblox view yet (Infoblox_Creds, Infoblox_Server and Infoblox_View)." |
|||
_err "Please set them via EXPORT ([username:password], [ip or hostname]) and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$Infoblox_View" ]; then |
|||
Infoblox_View="default" |
|||
fi |
|||
|
|||
## Save the credentials to the account file |
|||
_saveaccountconf Infoblox_Creds "$Infoblox_Creds" |
|||
_saveaccountconf Infoblox_Server "$Infoblox_Server" |
|||
_saveaccountconf Infoblox_View "$Infoblox_View" |
|||
|
|||
## Base64 encode the credentials |
|||
Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64) |
|||
|
|||
## Construct the HTTP Authorization header |
|||
export _H1="Accept-Language:en-US" |
|||
export _H2="Authorization: Basic $Infoblox_CredsEncoded" |
|||
|
|||
## Add the challenge record to the Infoblox grid member |
|||
result="$(_post "" "$baseurlnObject" "" "POST")" |
|||
|
|||
## Let's see if we get something intelligible back from the unit |
|||
if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then |
|||
_info "Successfully created the txt record" |
|||
return 0 |
|||
else |
|||
_err "Error encountered during record addition" |
|||
_err "$result" |
|||
return 1 |
|||
fi |
|||
|
|||
} |
|||
|
|||
dns_infoblox_rm() { |
|||
|
|||
## Nothing to see here, just some housekeeping |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
_info "Using Infoblox API" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
## Base64 encode the credentials |
|||
Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)" |
|||
|
|||
## Construct the HTTP Authorization header |
|||
export _H1="Accept-Language:en-US" |
|||
export _H2="Authorization: Basic $Infoblox_CredsEncoded" |
|||
|
|||
## Does the record exist? Let's check. |
|||
baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty" |
|||
result="$(_get "$baseurlnObject")" |
|||
|
|||
## Let's see if we get something intelligible back from the grid |
|||
if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then |
|||
## Extract the object reference |
|||
objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" |
|||
objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef" |
|||
## Delete them! All the stale records! |
|||
rmResult="$(_post "" "$objRmUrl" "" "DELETE")" |
|||
## Let's see if that worked |
|||
if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then |
|||
_info "Successfully deleted $objRef" |
|||
return 0 |
|||
else |
|||
_err "Error occurred during txt record delete" |
|||
_err "$rmResult" |
|||
return 1 |
|||
fi |
|||
else |
|||
_err "Record to delete didn't match an existing record" |
|||
_err "$result" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
@ -0,0 +1,311 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
#INWX_User="username" |
|||
# |
|||
#INWX_Password="password" |
|||
|
|||
INWX_Api="https://api.domrobot.com/xmlrpc/" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_inwx_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" |
|||
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" |
|||
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then |
|||
INWX_User="" |
|||
INWX_Password="" |
|||
_err "You don't specify inwx user and password yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable INWX_User "$INWX_User" |
|||
_saveaccountconf_mutable INWX_Password "$INWX_Password" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_info "Adding record" |
|||
_inwx_add_record "$_domain" "$_sub_domain" "$txtvalue" |
|||
|
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_inwx_rm() { |
|||
|
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" |
|||
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" |
|||
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then |
|||
INWX_User="" |
|||
INWX_Password="" |
|||
_err "You don't specify inwx user and password yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable INWX_User "$INWX_User" |
|||
_saveaccountconf_mutable INWX_Password "$INWX_Password" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug "Getting txt records" |
|||
|
|||
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>nameserver.info</methodName> |
|||
<params> |
|||
<param> |
|||
<value> |
|||
<struct> |
|||
<member> |
|||
<name>domain</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>type</name> |
|||
<value> |
|||
<string>TXT</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>name</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
</struct> |
|||
</value> |
|||
</param> |
|||
</params> |
|||
</methodCall>' "$_domain" "$_sub_domain") |
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
|
|||
if ! _contains "$response" "Command completed successfully"; then |
|||
_err "Error could not get txt records" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! printf "%s" "$response" | grep "count" >/dev/null; then |
|||
_info "Do not need to delete record" |
|||
else |
|||
_record_id=$(printf '%s' "$response" | _egrep_o '.*(<member><name>record){1}(.*)([0-9]+){1}' | _egrep_o '<name>id<\/name><value><int>[0-9]+' | _egrep_o '[0-9]+') |
|||
_info "Deleting record" |
|||
_inwx_delete_record "$_record_id" |
|||
fi |
|||
|
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
_inwx_login() { |
|||
|
|||
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>account.login</methodName> |
|||
<params> |
|||
<param> |
|||
<value> |
|||
<struct> |
|||
<member> |
|||
<name>user</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>pass</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
</struct> |
|||
</value> |
|||
</param> |
|||
</params> |
|||
</methodCall>' $INWX_User $INWX_Password) |
|||
|
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
|
|||
printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')" |
|||
|
|||
} |
|||
|
|||
_get_root() { |
|||
domain=$1 |
|||
_debug "get root" |
|||
|
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
_H1=$(_inwx_login) |
|||
export _H1 |
|||
xml_content='<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>nameserver.list</methodName> |
|||
</methodCall>' |
|||
|
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "$h"; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
|
|||
} |
|||
|
|||
_inwx_delete_record() { |
|||
record_id=$1 |
|||
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>nameserver.deleteRecord</methodName> |
|||
<params> |
|||
<param> |
|||
<value> |
|||
<struct> |
|||
<member> |
|||
<name>id</name> |
|||
<value> |
|||
<int>%s</int> |
|||
</value> |
|||
</member> |
|||
</struct> |
|||
</value> |
|||
</param> |
|||
</params> |
|||
</methodCall>' "$record_id") |
|||
|
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
|
|||
if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
|
|||
} |
|||
|
|||
_inwx_update_record() { |
|||
record_id=$1 |
|||
txtval=$2 |
|||
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>nameserver.updateRecord</methodName> |
|||
<params> |
|||
<param> |
|||
<value> |
|||
<struct> |
|||
<member> |
|||
<name>content</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>id</name> |
|||
<value> |
|||
<int>%s</int> |
|||
</value> |
|||
</member> |
|||
</struct> |
|||
</value> |
|||
</param> |
|||
</params> |
|||
</methodCall>' "$txtval" "$record_id") |
|||
|
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
|
|||
if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
|
|||
} |
|||
|
|||
_inwx_add_record() { |
|||
|
|||
domain=$1 |
|||
sub_domain=$2 |
|||
txtval=$3 |
|||
|
|||
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> |
|||
<methodCall> |
|||
<methodName>nameserver.createRecord</methodName> |
|||
<params> |
|||
<param> |
|||
<value> |
|||
<struct> |
|||
<member> |
|||
<name>domain</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>type</name> |
|||
<value> |
|||
<string>TXT</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>content</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
<member> |
|||
<name>name</name> |
|||
<value> |
|||
<string>%s</string> |
|||
</value> |
|||
</member> |
|||
</struct> |
|||
</value> |
|||
</param> |
|||
</params> |
|||
</methodCall>' "$domain" "$txtval" "$sub_domain") |
|||
|
|||
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" |
|||
|
|||
if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
} |
@ -0,0 +1,176 @@ |
|||
#!/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 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 |
|||
} |
@ -0,0 +1,95 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_knot_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_knot_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_checkKey || return 1 |
|||
[ -n "${KNOT_SERVER}" ] || KNOT_SERVER="localhost" |
|||
# save the dns server and key to the account.conf file. |
|||
_saveaccountconf KNOT_SERVER "${KNOT_SERVER}" |
|||
_saveaccountconf KNOT_KEY "${KNOT_KEY}" |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Domain does not exist." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\"" |
|||
|
|||
knsupdate -y "${KNOT_KEY}" <<EOF |
|||
server ${KNOT_SERVER} |
|||
zone ${_domain}. |
|||
update add ${fulldomain}. 60 TXT "${txtvalue}" |
|||
send |
|||
quit |
|||
EOF |
|||
|
|||
if [ $? -ne 0 ]; then |
|||
_err "Error updating domain." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Domain TXT record successfully added." |
|||
return 0 |
|||
} |
|||
|
|||
#Usage: dns_knot_rm _acme-challenge.www.domain.com |
|||
dns_knot_rm() { |
|||
fulldomain=$1 |
|||
_checkKey || return 1 |
|||
[ -n "${KNOT_SERVER}" ] || KNOT_SERVER="localhost" |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Domain does not exist." |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Removing ${fulldomain}. TXT" |
|||
|
|||
knsupdate -y "${KNOT_KEY}" <<EOF |
|||
server ${KNOT_SERVER} |
|||
zone ${_domain}. |
|||
update del ${fulldomain}. TXT |
|||
send |
|||
quit |
|||
EOF |
|||
|
|||
if [ $? -ne 0 ]; then |
|||
_err "error updating domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Domain TXT record successfully deleted." |
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
# _acme-challenge.www.domain.com |
|||
# returns |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" |
|||
i=$(_math "$i" - 1) |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) |
|||
if [ -z "$h" ]; then |
|||
return 1 |
|||
fi |
|||
_domain="$h" |
|||
return 0 |
|||
done |
|||
_debug "$domain not found" |
|||
return 1 |
|||
} |
|||
|
|||
_checkKey() { |
|||
if [ -z "${KNOT_KEY}" ]; then |
|||
_err "You must specify a TSIG key to authenticate the request." |
|||
return 1 |
|||
fi |
|||
} |
@ -0,0 +1,78 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# dns api wrapper of lexicon for acme.sh |
|||
|
|||
# https://github.com/AnalogJ/lexicon |
|||
lexicon_cmd="lexicon" |
|||
|
|||
wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_lexicon_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999) |
|||
|
|||
if ! _exists "$lexicon_cmd"; then |
|||
_err "Please install $lexicon_cmd first: $wiki" |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$PROVIDER" ]; then |
|||
PROVIDER="" |
|||
_err "Please define env PROVIDER first: $wiki" |
|||
return 1 |
|||
fi |
|||
|
|||
_savedomainconf PROVIDER "$PROVIDER" |
|||
export PROVIDER |
|||
|
|||
# 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") |
|||
_secure_debug "$Lx_name" "$Lx_name_v" |
|||
if [ "$Lx_name_v" ]; then |
|||
_saveaccountconf "$Lx_name" "$Lx_name_v" |
|||
eval export "$Lx_name" |
|||
fi |
|||
|
|||
# shellcheck disable=SC2018,SC2019 |
|||
Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z') |
|||
Lx_token_v=$(eval echo \$"$Lx_token") |
|||
_secure_debug "$Lx_token" "$Lx_token_v" |
|||
if [ "$Lx_token_v" ]; then |
|||
_saveaccountconf "$Lx_token" "$Lx_token_v" |
|||
eval export "$Lx_token" |
|||
fi |
|||
|
|||
# shellcheck disable=SC2018,SC2019 |
|||
Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z') |
|||
Lx_password_v=$(eval echo \$"$Lx_password") |
|||
_secure_debug "$Lx_password" "$Lx_password_v" |
|||
if [ "$Lx_password_v" ]; then |
|||
_saveaccountconf "$Lx_password" "$Lx_password_v" |
|||
eval export "$Lx_password" |
|||
fi |
|||
|
|||
# shellcheck disable=SC2018,SC2019 |
|||
Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z') |
|||
Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") |
|||
_secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v" |
|||
if [ "$Lx_domaintoken_v" ]; then |
|||
eval export "$Lx_domaintoken" |
|||
_saveaccountconf "$Lx_domaintoken" "$Lx_domaintoken_v" |
|||
fi |
|||
|
|||
$lexicon_cmd "$PROVIDER" create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" |
|||
|
|||
} |
|||
|
|||
#fulldomain |
|||
dns_lexicon_rm() { |
|||
fulldomain=$1 |
|||
|
|||
} |
@ -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" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" |
|||
|
|||
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" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" |
|||
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 |
|||
} |
@ -0,0 +1,154 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# bug reports to dev@1e.ca |
|||
|
|||
# |
|||
#LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#LUA_Email="user@luadns.net" |
|||
|
|||
LUA_Api="https://api.luadns.com/v1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_lua_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" |
|||
LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" |
|||
LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) |
|||
|
|||
if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then |
|||
LUA_Key="" |
|||
LUA_Email="" |
|||
_err "You don't specify luadns api key and email yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable LUA_Key "$LUA_Key" |
|||
_saveaccountconf_mutable LUA_Email "$LUA_Email" |
|||
|
|||
_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" |
|||
|
|||
_info "Adding record" |
|||
if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then |
|||
if _contains "$response" "$fulldomain"; then |
|||
_info "Added" |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
#fulldomain |
|||
dns_lua_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" |
|||
LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" |
|||
LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) |
|||
_debug "First detect the root zone" |
|||
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" |
|||
_LUA_rest GET "zones/${_domain_id}/records" |
|||
|
|||
count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") |
|||
_debug count "$count" |
|||
if [ "$count" = "0" ]; then |
|||
_info "Don't need to remove." |
|||
else |
|||
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" |
|||
if [ -z "$record_id" ]; then |
|||
_err "Can not get record id to remove." |
|||
return 1 |
|||
fi |
|||
if ! _LUA_rest DELETE "/zones/$_domain_id/records/$record_id"; then |
|||
_err "Delete record error." |
|||
return 1 |
|||
fi |
|||
_contains "$response" "$record_id" |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
if ! _LUA_rest GET "zones"; then |
|||
return 1 |
|||
fi |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\":\"$h\""; then |
|||
_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 |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_LUA_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Accept: application/json" |
|||
export _H2="Authorization: Basic $LUA_auth" |
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$LUA_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,157 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# bug reports to dev@1e.ca |
|||
|
|||
# ME_Key=qmlkdjflmkqdjf |
|||
# ME_Secret=qmsdlkqmlksdvnnpae |
|||
|
|||
ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_me_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$ME_Key" ] || [ -z "$ME_Secret" ]; then |
|||
ME_Key="" |
|||
ME_Secret="" |
|||
_err "You didn't specify DNSMadeEasy api key and secret yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf ME_Key "$ME_Key" |
|||
_saveaccountconf ME_Secret "$ME_Secret" |
|||
|
|||
_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" |
|||
_me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" |
|||
|
|||
if ! _contains "$response" "\"totalRecords\":"; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Adding record" |
|||
if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then |
|||
if printf -- "%s" "$response" | grep \"id\": >/dev/null; then |
|||
_info "Added" |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
} |
|||
|
|||
#fulldomain |
|||
dns_me_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" |
|||
_me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" |
|||
|
|||
count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | 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 ",\"value\":\"..$txtvalue..\",\"id\":[^,]*" | cut -d : -f 3 | 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 ! _me_rest DELETE "$_domain_id/records/$record_id"; then |
|||
_err "Delete record error." |
|||
return 1 |
|||
fi |
|||
_contains "$response" '' |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _me_rest GET "name?domainname=$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\":\"$h\""; then |
|||
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}') |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_me_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") |
|||
hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) |
|||
|
|||
export _H1="x-dnsme-apiKey: $ME_Key" |
|||
export _H2="x-dnsme-requestDate: $cdate" |
|||
export _H3="x-dnsme-hmac: $hmac" |
|||
|
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$ME_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$ME_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,35 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Here is a sample custom api script. |
|||
#This file name is "dns_myapi.sh" |
|||
#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. |
|||
#returns 0 means success, otherwise error. |
|||
# |
|||
#Author: Neilpang |
|||
#Report Bugs here: https://github.com/Neilpang/acme.sh |
|||
# |
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_myapi_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_info "Using myapi" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
_err "Not implemented!" |
|||
return 1 |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_myapi_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_info "Using myapi" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
@ -0,0 +1,166 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Author: RaidenII |
|||
#Created 06/28/2017 |
|||
#Updated 03/01/2018, rewrote to support name.com API v4 |
|||
#Utilize name.com API to finish dns-01 verifications. |
|||
######## Public functions ##################### |
|||
|
|||
Namecom_API="https://api.name.com/v4" |
|||
|
|||
#Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_namecom_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
# First we need name.com credentials. |
|||
if [ -z "$Namecom_Username" ]; then |
|||
Namecom_Username="" |
|||
_err "Username for name.com is missing." |
|||
_err "Please specify that in your environment variable." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$Namecom_Token" ]; then |
|||
Namecom_Token="" |
|||
_err "API token for name.com is missing." |
|||
_err "Please specify that in your environment variable." |
|||
return 1 |
|||
fi |
|||
|
|||
# Save them in configuration. |
|||
_saveaccountconf Namecom_Username "$Namecom_Username" |
|||
_saveaccountconf Namecom_Token "$Namecom_Token" |
|||
|
|||
# Login in using API |
|||
if ! _namecom_login; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Find domain in domain list. |
|||
if ! _namecom_get_root "$fulldomain"; then |
|||
_err "Unable to find domain specified." |
|||
return 1 |
|||
fi |
|||
|
|||
# Add TXT record. |
|||
_namecom_addtxt_json="{\"host\":\"$_sub_domain\",\"type\":\"TXT\",\"answer\":\"$txtvalue\",\"ttl\":\"300\"}" |
|||
if _namecom_rest POST "domains/$_domain/records" "$_namecom_addtxt_json"; then |
|||
_retvalue=$(printf "%s\n" "$response" | _egrep_o "\"$_sub_domain\"") |
|||
if [ "$_retvalue" ]; then |
|||
_info "Successfully added TXT record, ready for validation." |
|||
return 0 |
|||
else |
|||
_err "Unable to add the DNS record." |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_namecom_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! _namecom_login; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Find domain in domain list. |
|||
if ! _namecom_get_root "$fulldomain"; then |
|||
_err "Unable to find domain specified." |
|||
return 1 |
|||
fi |
|||
|
|||
# Get the record id. |
|||
if _namecom_rest GET "domains/$_domain/records"; then |
|||
_record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]+,\"domainName\":\"$_domain\",\"host\":\"$_sub_domain\",\"fqdn\":\"$fulldomain.\",\"type\":\"TXT\",\"answer\":\"$txtvalue\"" | cut -d \" -f 3 | _egrep_o [0-9]+) |
|||
_debug record_id "$_record_id" |
|||
if [ "$_record_id" ]; then |
|||
_info "Successfully retrieved the record id for ACME challenge." |
|||
else |
|||
_err "Unable to retrieve the record id." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
# Remove the DNS record using record id. |
|||
if _namecom_rest DELETE "domains/$_domain/records/$_record_id"; then |
|||
_info "Successfully removed the TXT record." |
|||
return 0 |
|||
else |
|||
_err "Unable to delete record id." |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
_namecom_rest() { |
|||
method=$1 |
|||
param=$2 |
|||
data=$3 |
|||
|
|||
export _H1="Authorization: Basic $_namecom_auth" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
if [ "$method" != "GET" ]; then |
|||
response="$(_post "$data" "$Namecom_API/$param" "" "$method")" |
|||
else |
|||
response="$(_get "$Namecom_API/$param")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $param" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
|||
|
|||
_namecom_login() { |
|||
# Auth string |
|||
# Name.com API v4 uses http basic auth to authenticate |
|||
# need to convert the token for http auth |
|||
_namecom_auth=$(printf "%s:%s" "$Namecom_Username" "$Namecom_Token" | base64) |
|||
|
|||
if _namecom_rest GET "hello"; then |
|||
retcode=$(printf "%s\n" "$response" | _egrep_o "\"username\"\:\"$Namecom_Username\"") |
|||
if [ "$retcode" ]; then |
|||
_info "Successfully logged in." |
|||
else |
|||
_err "Logging in failed." |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
_namecom_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
if ! _namecom_rest GET "domains"; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Need to exclude the last field (tld) |
|||
numfields=$(echo "$domain" | _egrep_o "\." | wc -l) |
|||
while [ $i -le "$numfields" ]; do |
|||
host=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug host "$host" |
|||
if [ -z "$host" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "$host"; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$host" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
@ -0,0 +1,137 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Author: meowthink |
|||
#Created 01/14/2017 |
|||
#Utilize namesilo.com API to finish dns-01 verifications. |
|||
|
|||
Namesilo_API="https://www.namesilo.com/api" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_namesilo_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$Namesilo_Key" ]; then |
|||
Namesilo_Key="" |
|||
_err "API token for namesilo.com is missing." |
|||
_err "Please specify that in your environment variable." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf Namesilo_Key "$Namesilo_Key" |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Unable to find domain specified." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug txtvalue "$txtvalue" |
|||
if _namesilo_rest GET "dnsAddRecord?version=1&type=xml&key=$Namesilo_Key&domain=$_domain&rrtype=TXT&rrhost=$_sub_domain&rrvalue=$txtvalue"; then |
|||
retcode=$(printf "%s\n" "$response" | _egrep_o "<code>300") |
|||
if [ "$retcode" ]; then |
|||
_info "Successfully added TXT record, ready for validation." |
|||
return 0 |
|||
else |
|||
_err "Unable to add the DNS record." |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
#Usage: fulldomain txtvalue |
|||
#Remove the txt record after validation. |
|||
dns_namesilo_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! _get_root "$fulldomain"; then |
|||
_err "Unable to find domain specified." |
|||
return 1 |
|||
fi |
|||
|
|||
# Get the record id. |
|||
if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then |
|||
retcode=$(printf "%s\n" "$response" | _egrep_o "<code>300") |
|||
if [ "$retcode" ]; then |
|||
_record_id=$(printf "%s\n" "$response" | _egrep_o "<record_id>([^<]*)</record_id><type>TXT</type><host>$fulldomain</host>" | _egrep_o "<record_id>([^<]*)</record_id>" | sed -r "s/<record_id>([^<]*)<\/record_id>/\1/" | tail -n 1) |
|||
_debug record_id "$_record_id" |
|||
_info "Successfully retrieved the record id for ACME challenge." |
|||
else |
|||
_err "Unable to retrieve the record id." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
# Remove the DNS record using record id. |
|||
if _namesilo_rest GET "dnsDeleteRecord?version=1&type=xml&key=$Namesilo_Key&domain=$_domain&rrid=$_record_id"; then |
|||
retcode=$(printf "%s\n" "$response" | _egrep_o "<code>300") |
|||
if [ "$retcode" ]; then |
|||
_info "Successfully removed the TXT record." |
|||
return 0 |
|||
else |
|||
_err "Unable to remove the DNS record." |
|||
return 1 |
|||
fi |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
# _acme-challenge.www.domain.com |
|||
# returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
if ! _namesilo_rest GET "listDomains?version=1&type=xml&key=$Namesilo_Key"; then |
|||
return 1 |
|||
fi |
|||
|
|||
# Need to exclude the last field (tld) |
|||
numfields=$(echo "$domain" | _egrep_o "\." | wc -l) |
|||
while [ $i -le "$numfields" ]; do |
|||
host=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug host "$host" |
|||
if [ -z "$host" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "$host"; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$host" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_namesilo_rest() { |
|||
method=$1 |
|||
param=$2 |
|||
data=$3 |
|||
|
|||
if [ "$method" != "GET" ]; then |
|||
response="$(_post "$data" "$Namesilo_API/$param" "" "$method")" |
|||
else |
|||
response="$(_get "$Namesilo_API/$param")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $param" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,158 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# bug reports to dev@1e.ca |
|||
|
|||
# |
|||
#NS1_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
|
|||
NS1_Api="https://api.nsone.net/v1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_nsone_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$NS1_Key" ]; then |
|||
NS1_Key="" |
|||
_err "You didn't specify nsone dns api key yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf NS1_Key "$NS1_Key" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug "Getting txt records" |
|||
_nsone_rest GET "zones/${_domain}" |
|||
|
|||
if ! _contains "$response" "\"records\":"; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") |
|||
_debug count "$count" |
|||
if [ "$count" = "0" ]; then |
|||
_info "Adding record" |
|||
|
|||
if _nsone_rest PUT "zones/$_domain/$fulldomain/TXT" "{\"answers\":[{\"answer\":[\"$txtvalue\"]}],\"type\":\"TXT\",\"domain\":\"$fulldomain\",\"zone\":\"$_domain\"}"; then |
|||
if _contains "$response" "$fulldomain"; then |
|||
_info "Added" |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
else |
|||
_info "Updating record" |
|||
prev_txt=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",\"short_answers\":\[\"[^,]*\]" | _head_n 1 | cut -d: -f3 | cut -d, -f1) |
|||
_debug "prev_txt" "$prev_txt" |
|||
|
|||
_nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]},{\"answer\": $prev_txt}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\"}" |
|||
if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then |
|||
_info "Updated!" |
|||
#todo: check if the record takes effect |
|||
return 0 |
|||
fi |
|||
_err "Update error" |
|||
return 1 |
|||
fi |
|||
|
|||
} |
|||
|
|||
#fulldomain |
|||
dns_nsone_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_debug "Getting txt records" |
|||
_nsone_rest GET "zones/${_domain}/$fulldomain/TXT" |
|||
|
|||
count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",.*\"type\":\"TXT\"" | wc -l | tr -d " ") |
|||
_debug count "$count" |
|||
if [ "$count" = "0" ]; then |
|||
_info "Don't need to remove." |
|||
else |
|||
if ! _nsone_rest DELETE "zones/${_domain}/$fulldomain/TXT"; then |
|||
_err "Delete record error." |
|||
return 1 |
|||
fi |
|||
_contains "$response" "" |
|||
fi |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
if ! _nsone_rest GET "zones"; then |
|||
return 1 |
|||
fi |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"zone\":\"$h\""; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_nsone_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Accept: application/json" |
|||
export _H2="X-NSONE-Key: $NS1_Key" |
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$NS1_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$NS1_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -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 |
|||
} |
@ -0,0 +1,318 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#Application Key |
|||
#OVH_AK="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#Application Secret |
|||
#OVH_AS="sdfsafsdfsdfdsfsdfsa" |
|||
# |
|||
#Consumer Key |
|||
#OVH_CK="sdfsdfsdfsdfsdfdsf" |
|||
|
|||
#OVH_END_POINT=ovh-eu |
|||
|
|||
#'ovh-eu' |
|||
OVH_EU='https://eu.api.ovh.com/1.0' |
|||
|
|||
#'ovh-ca': |
|||
OVH_CA='https://ca.api.ovh.com/1.0' |
|||
|
|||
#'kimsufi-eu' |
|||
KSF_EU='https://eu.api.kimsufi.com/1.0' |
|||
|
|||
#'kimsufi-ca' |
|||
KSF_CA='https://ca.api.kimsufi.com/1.0' |
|||
|
|||
#'soyoustart-eu' |
|||
SYS_EU='https://eu.api.soyoustart.com/1.0' |
|||
|
|||
#'soyoustart-ca' |
|||
SYS_CA='https://ca.api.soyoustart.com/1.0' |
|||
|
|||
#'runabove-ca' |
|||
RAV_CA='https://api.runabove.com/1.0' |
|||
|
|||
wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api" |
|||
|
|||
ovh_success="https://github.com/Neilpang/acme.sh/wiki/OVH-Success" |
|||
|
|||
_ovh_get_api() { |
|||
_ogaep="$1" |
|||
|
|||
case "${_ogaep}" in |
|||
|
|||
ovh-eu | ovheu) |
|||
printf "%s" $OVH_EU |
|||
return |
|||
;; |
|||
ovh-ca | ovhca) |
|||
printf "%s" $OVH_CA |
|||
return |
|||
;; |
|||
kimsufi-eu | kimsufieu) |
|||
printf "%s" $KSF_EU |
|||
return |
|||
;; |
|||
kimsufi-ca | kimsufica) |
|||
printf "%s" $KSF_CA |
|||
return |
|||
;; |
|||
soyoustart-eu | soyoustarteu) |
|||
printf "%s" $SYS_EU |
|||
return |
|||
;; |
|||
soyoustart-ca | soyoustartca) |
|||
printf "%s" $SYS_CA |
|||
return |
|||
;; |
|||
runabove-ca | runaboveca) |
|||
printf "%s" $RAV_CA |
|||
return |
|||
;; |
|||
|
|||
*) |
|||
|
|||
_err "Unknown parameter : $1" |
|||
return 1 |
|||
;; |
|||
esac |
|||
} |
|||
|
|||
_initAuth() { |
|||
OVH_AK="${OVH_AK:-$(_readaccountconf_mutable OVH_AK)}" |
|||
OVH_AS="${OVH_AS:-$(_readaccountconf_mutable OVH_AS)}" |
|||
|
|||
if [ -z "$OVH_AK" ] || [ -z "$OVH_AS" ]; then |
|||
OVH_AK="" |
|||
OVH_AS="" |
|||
_err "You don't specify OVH application key and application secret yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then |
|||
_info "It seems that your ovh key is changed, let's clear consumer key first." |
|||
_clearaccountconf OVH_CK |
|||
fi |
|||
_saveaccountconf_mutable OVH_AK "$OVH_AK" |
|||
_saveaccountconf_mutable OVH_AS "$OVH_AS" |
|||
|
|||
OVH_END_POINT="${OVH_END_POINT:-$(_readaccountconf_mutable OVH_END_POINT)}" |
|||
if [ -z "$OVH_END_POINT" ]; then |
|||
OVH_END_POINT="ovh-eu" |
|||
fi |
|||
_info "Using OVH endpoint: $OVH_END_POINT" |
|||
if [ "$OVH_END_POINT" != "ovh-eu" ]; then |
|||
_saveaccountconf_mutable OVH_END_POINT "$OVH_END_POINT" |
|||
fi |
|||
|
|||
OVH_API="$(_ovh_get_api $OVH_END_POINT)" |
|||
_debug OVH_API "$OVH_API" |
|||
|
|||
OVH_CK="${OVH_CK:-$(_readaccountconf_mutable OVH_CK)}" |
|||
if [ -z "$OVH_CK" ]; then |
|||
_info "OVH consumer key is empty, Let's get one:" |
|||
if ! _ovh_authentication; then |
|||
_err "Can not get consumer key." |
|||
fi |
|||
#return and wait for retry. |
|||
return 1 |
|||
fi |
|||
|
|||
_info "Checking authentication" |
|||
|
|||
if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL"; then |
|||
_err "The consumer key is invalid: $OVH_CK" |
|||
_err "Please retry to create a new one." |
|||
_clearaccountconf OVH_CK |
|||
return 1 |
|||
fi |
|||
_info "Consumer key is ok." |
|||
return 0 |
|||
} |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_ovh_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! _initAuth; then |
|||
return 1 |
|||
fi |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
_info "Adding record" |
|||
if _ovh_rest POST "domain/zone/$_domain/record" "{\"fieldType\":\"TXT\",\"subDomain\":\"$_sub_domain\",\"target\":\"$txtvalue\",\"ttl\":60}"; then |
|||
if _contains "$response" "$txtvalue"; then |
|||
_ovh_rest POST "domain/zone/$_domain/refresh" |
|||
_debug "Refresh:$response" |
|||
_info "Added, sleep 10 seconds." |
|||
_sleep 10 |
|||
return 0 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
return 1 |
|||
|
|||
} |
|||
|
|||
#fulldomain |
|||
dns_ovh_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if ! _initAuth; then |
|||
return 1 |
|||
fi |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
_debug "Getting txt records" |
|||
if ! _ovh_rest GET "domain/zone/$_domain/record?fieldType=TXT&subDomain=$_sub_domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
for rid in $(echo "$response" | tr '][,' ' '); do |
|||
_debug rid "$rid" |
|||
if ! _ovh_rest GET "domain/zone/$_domain/record/$rid"; then |
|||
return 1 |
|||
fi |
|||
if _contains "$response" "\"target\":\"$txtvalue\""; then |
|||
_debug "Found txt id:$rid" |
|||
if ! _ovh_rest DELETE "domain/zone/$_domain/record/$rid"; then |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
fi |
|||
done |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
_ovh_authentication() { |
|||
|
|||
_H1="X-Ovh-Application: $OVH_AK" |
|||
_H2="Content-type: application/json" |
|||
_H3="" |
|||
_H4="" |
|||
|
|||
_ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' |
|||
|
|||
response="$(_post "$_ovhdata" "$OVH_API/auth/credential")" |
|||
_debug3 response "$response" |
|||
validationUrl="$(echo "$response" | _egrep_o "validationUrl\":\"[^\"]*\"" | _egrep_o "http.*\"" | tr -d '"')" |
|||
if [ -z "$validationUrl" ]; then |
|||
_err "Unable to get validationUrl" |
|||
return 1 |
|||
fi |
|||
_debug validationUrl "$validationUrl" |
|||
|
|||
consumerKey="$(echo "$response" | _egrep_o "consumerKey\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')" |
|||
if [ -z "$consumerKey" ]; then |
|||
_err "Unable to get consumerKey" |
|||
return 1 |
|||
fi |
|||
_secure_debug consumerKey "$consumerKey" |
|||
|
|||
OVH_CK="$consumerKey" |
|||
_saveaccountconf OVH_CK "$OVH_CK" |
|||
|
|||
_info "Please open this link to do authentication: $(__green "$validationUrl")" |
|||
|
|||
_info "Here is a guide for you: $(__green "$wiki")" |
|||
_info "Please retry after the authentication is done." |
|||
|
|||
} |
|||
|
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _ovh_rest GET "domain/zone/$h"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_ovh_timestamp() { |
|||
_H1="" |
|||
_H2="" |
|||
_H3="" |
|||
_H4="" |
|||
_H5="" |
|||
_get "$OVH_API/auth/time" "" 30 |
|||
} |
|||
|
|||
_ovh_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
_ovh_url="$OVH_API/$ep" |
|||
_debug2 _ovh_url "$_ovh_url" |
|||
_ovh_t="$(_ovh_timestamp)" |
|||
_debug2 _ovh_t "$_ovh_t" |
|||
_ovh_p="$OVH_AS+$OVH_CK+$m+$_ovh_url+$data+$_ovh_t" |
|||
_secure_debug _ovh_p "$_ovh_p" |
|||
_ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)" |
|||
_debug2 _ovh_hex "$_ovh_hex" |
|||
|
|||
export _H1="X-Ovh-Application: $OVH_AK" |
|||
export _H2="X-Ovh-Signature: \$1\$$_ovh_hex" |
|||
_debug2 _H2 "$_H2" |
|||
export _H3="X-Ovh-Timestamp: $_ovh_t" |
|||
export _H4="X-Ovh-Consumer: $OVH_CK" |
|||
export _H5="Content-Type: application/json;charset=utf-8" |
|||
if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$_ovh_url" "" "$m")" |
|||
else |
|||
response="$(_get "$_ovh_url")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ] || _contains "$response" "INVALID_CREDENTIAL"; then |
|||
_err "error $response" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,184 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#PowerDNS Embedded API |
|||
#https://doc.powerdns.com/md/httpapi/api_spec/ |
|||
# |
|||
#PDNS_Url="http://ns.example.com:8081" |
|||
#PDNS_ServerId="localhost" |
|||
#PDNS_Token="0123456789ABCDEF" |
|||
#PDNS_Ttl=60 |
|||
|
|||
DEFAULT_PDNS_TTL=60 |
|||
|
|||
######## Public functions ##################### |
|||
#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" |
|||
#fulldomain |
|||
#txtvalue |
|||
dns_pdns_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$PDNS_Url" ]; then |
|||
PDNS_Url="" |
|||
_err "You don't specify PowerDNS address." |
|||
_err "Please set PDNS_Url and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$PDNS_ServerId" ]; then |
|||
PDNS_ServerId="" |
|||
_err "You don't specify PowerDNS server id." |
|||
_err "Please set you PDNS_ServerId and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$PDNS_Token" ]; then |
|||
PDNS_Token="" |
|||
_err "You don't specify PowerDNS token." |
|||
_err "Please create you PDNS_Token and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if [ -z "$PDNS_Ttl" ]; then |
|||
PDNS_Ttl="$DEFAULT_PDNS_TTL" |
|||
fi |
|||
|
|||
#save the api addr and key to the account conf file. |
|||
_saveaccountconf PDNS_Url "$PDNS_Url" |
|||
_saveaccountconf PDNS_ServerId "$PDNS_ServerId" |
|||
_saveaccountconf PDNS_Token "$PDNS_Token" |
|||
|
|||
if [ "$PDNS_Ttl" != "$DEFAULT_PDNS_TTL" ]; then |
|||
_saveaccountconf PDNS_Ttl "$PDNS_Ttl" |
|||
fi |
|||
|
|||
_debug "Detect root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
_debug _domain "$_domain" |
|||
|
|||
if ! set_record "$_domain" "$fulldomain" "$txtvalue"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
#fulldomain |
|||
dns_pdns_rm() { |
|||
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() { |
|||
_info "Adding record" |
|||
root=$1 |
|||
full=$2 |
|||
txtvalue=$3 |
|||
|
|||
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." |
|||
return 1 |
|||
fi |
|||
|
|||
if ! notify_slaves "$root"; then |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
} |
|||
|
|||
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 |
|||
#returns |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
domain=$1 |
|||
i=1 |
|||
|
|||
if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then |
|||
_zones_response="$response" |
|||
fi |
|||
|
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
if [ -z "$h" ]; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$_zones_response" "\"name\": \"$h.\""; then |
|||
_domain="$h" |
|||
return 0 |
|||
fi |
|||
|
|||
i=$(_math $i + 1) |
|||
done |
|||
_debug "$domain not found" |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_pdns_rest() { |
|||
method=$1 |
|||
ep=$2 |
|||
data=$3 |
|||
|
|||
export _H1="X-API-Key: $PDNS_Token" |
|||
|
|||
if [ ! "$method" = "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$PDNS_Url$ep" "" "$method")" |
|||
else |
|||
response="$(_get "$PDNS_Url$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
|
|||
return 0 |
|||
} |
@ -0,0 +1,161 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
#SL_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
|
|||
SL_Api="https://api.selectel.ru/domains/v1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_selectel_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" |
|||
|
|||
if [ -z "$SL_Key" ]; then |
|||
SL_Key="" |
|||
_err "You don't specify selectel.ru api key yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key to the account conf file. |
|||
_saveaccountconf_mutable SL_Key "$SL_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" |
|||
|
|||
_info "Adding record" |
|||
if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then |
|||
if _contains "$response" "$txtvalue" || _contains "$response" "record_already_exists"; then |
|||
_info "Added, OK" |
|||
return 0 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
return 1 |
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_selectel_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" |
|||
|
|||
if [ -z "$SL_Key" ]; then |
|||
SL_Key="" |
|||
_err "You don't specify slectel api key yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
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" |
|||
|
|||
_debug "Getting txt records" |
|||
_sl_rest GET "/${_domain_id}/records/" |
|||
|
|||
if ! _contains "$response" "$txtvalue"; then |
|||
_err "Txt record not found" |
|||
return 1 |
|||
fi |
|||
|
|||
_record_seg="$(echo "$response" | _egrep_o "\"content\" *: *\"$txtvalue\"[^}]*}")" |
|||
_debug2 "_record_seg" "$_record_seg" |
|||
if [ -z "$_record_seg" ]; then |
|||
_err "can not find _record_seg" |
|||
return 1 |
|||
fi |
|||
|
|||
_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)" |
|||
_debug2 "_record_id" "$_record_id" |
|||
if [ -z "$_record_id" ]; then |
|||
_err "can not find _record_id" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _sl_rest DELETE "/$_domain_id/records/$_record_id"; then |
|||
_err "Delete record error." |
|||
return 1 |
|||
fi |
|||
return 0 |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
|
|||
if ! _sl_rest GET "/"; then |
|||
return 1 |
|||
fi |
|||
|
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"name\": \"$h\","; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
_debug "Getting domain id for $h" |
|||
if ! _sl_rest GET "/$h"; then |
|||
return 1 |
|||
fi |
|||
_domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" |
|||
return 0 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_sl_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="X-Token: $SL_Key" |
|||
export _H2="Content-Type: application/json" |
|||
|
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$SL_Api/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$SL_Api/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,170 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
########## |
|||
# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/Neilpang/acme.sh) |
|||
# |
|||
# Usage: |
|||
# export SERVERCOW_API_Username=username |
|||
# export SERVERCOW_API_Password=password |
|||
# acme.sh --issue -d example.com --dns dns_servercow |
|||
# |
|||
# Issues: |
|||
# Any issues / questions / suggestions can be posted here: |
|||
# https://github.com/jhartlep/servercow-dns-api/issues |
|||
# |
|||
# Author: Jens Hartlep |
|||
########## |
|||
|
|||
SERVERCOW_API="https://api.servercow.de/dns/v1/domains" |
|||
|
|||
# Usage dns_servercow_add _acme-challenge.www.domain.com "abcdefghijklmnopqrstuvwxyz" |
|||
dns_servercow_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
_info "Using servercow" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$txtvalue" |
|||
|
|||
SERVERCOW_API_Username="${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}" |
|||
SERVERCOW_API_Password="${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}" |
|||
if [ -z "$SERVERCOW_API_Username" ] || [ -z "$SERVERCOW_API_Password" ]; then |
|||
SERVERCOW_API_Username="" |
|||
SERVERCOW_API_Password="" |
|||
_err "You don't specify servercow api username and password yet." |
|||
_err "Please create your username and password and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
# save the credentials to the account conf file |
|||
_saveaccountconf_mutable SERVERCOW_API_Username "$SERVERCOW_API_Username" |
|||
_saveaccountconf_mutable SERVERCOW_API_Password "$SERVERCOW_API_Password" |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then |
|||
if printf -- "%s" "$response" | grep "ok" >/dev/null; then |
|||
_info "Added, OK" |
|||
return 0 |
|||
else |
|||
_err "add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "add txt record error." |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
# Usage fulldomain txtvalue |
|||
# Remove the txt record after validation |
|||
dns_servercow_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
_info "Using servercow" |
|||
_debug fulldomain "$fulldomain" |
|||
_debug txtvalue "$fulldomain" |
|||
|
|||
SERVERCOW_API_Username="${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}" |
|||
SERVERCOW_API_Password="${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}" |
|||
if [ -z "$SERVERCOW_API_Username" ] || [ -z "$SERVERCOW_API_Password" ]; then |
|||
SERVERCOW_API_Username="" |
|||
SERVERCOW_API_Password="" |
|||
_err "You don't specify servercow api username and password yet." |
|||
_err "Please create your username and password and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
_debug "First detect the root zone" |
|||
if ! _get_root "$fulldomain"; then |
|||
_err "invalid domain" |
|||
return 1 |
|||
fi |
|||
|
|||
_debug _sub_domain "$_sub_domain" |
|||
_debug _domain "$_domain" |
|||
|
|||
if _servercow_api DELETE "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\"}"; then |
|||
if printf -- "%s" "$response" | grep "ok" >/dev/null; then |
|||
_info "Deleted, OK" |
|||
_contains "$response" '"message":"ok"' |
|||
else |
|||
_err "delete txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
|
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
# _acme-challenge.www.domain.com |
|||
# returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
_get_root() { |
|||
fulldomain=$1 |
|||
i=2 |
|||
p=1 |
|||
|
|||
while true; do |
|||
_domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) |
|||
|
|||
_debug _domain "$_domain" |
|||
if [ -z "$_domain" ]; then |
|||
# not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _servercow_api GET "$_domain"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" '"error":"no such domain in user context"' >/dev/null; then |
|||
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p) |
|||
if [ -z "$_sub_domain" ]; then |
|||
# not valid |
|||
return 1 |
|||
fi |
|||
|
|||
return 0 |
|||
fi |
|||
|
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
_servercow_api() { |
|||
method=$1 |
|||
domain=$2 |
|||
data="$3" |
|||
|
|||
export _H1="Content-Type: application/json" |
|||
export _H2="X-Auth-Username: $SERVERCOW_API_Username" |
|||
export _H3="X-Auth-Password: $SERVERCOW_API_Password" |
|||
|
|||
if [ "$method" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$SERVERCOW_API/$domain" "" "$method")" |
|||
else |
|||
response="$(_get "$SERVERCOW_API/$domain")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $domain" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,202 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
#UNO_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#UNO_User="UExxxxxx" |
|||
|
|||
Uno_Api="https://api.unoeuro.com/1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_unoeuro_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}" |
|||
UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}" |
|||
if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then |
|||
UNO_Key="" |
|||
UNO_User="" |
|||
_err "You haven't specified a UnoEuro api key and account yet." |
|||
_err "Please create your key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$UNO_User" "UE"; then |
|||
_err "It seems that the UNO_User=$UNO_User is not a valid username." |
|||
_err "Please check and retry." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key and email to the account conf file. |
|||
_saveaccountconf_mutable UNO_Key "$UNO_Key" |
|||
_saveaccountconf_mutable UNO_User "$UNO_User" |
|||
|
|||
_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" |
|||
_uno_rest GET "my/products/$h/dns/records" |
|||
|
|||
if ! _contains "$response" "\"status\": 200" >/dev/null; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "$_sub_domain" >/dev/null; then |
|||
_info "Adding record" |
|||
|
|||
if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then |
|||
if _contains "$response" "\"status\": 200" >/dev/null; then |
|||
_info "Added, OK" |
|||
return 0 |
|||
else |
|||
_err "Add txt record error." |
|||
return 1 |
|||
fi |
|||
fi |
|||
_err "Add txt record error." |
|||
else |
|||
_info "Updating record" |
|||
record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1) |
|||
record_line_number=$(_math "$record_line_number" - 1) |
|||
record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") |
|||
_debug "record_id" "$record_id" |
|||
|
|||
_uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}" |
|||
if _contains "$response" "\"status\": 200" >/dev/null; then |
|||
_info "Updated, OK" |
|||
return 0 |
|||
fi |
|||
_err "Update error" |
|||
return 1 |
|||
fi |
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_unoeuro_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}" |
|||
UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}" |
|||
if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then |
|||
UNO_Key="" |
|||
UNO_User="" |
|||
_err "You haven't specified a UnoEuro api key and account yet." |
|||
_err "Please create your key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$UNO_User" "UE"; then |
|||
_err "It seems that the UNO_User=$UNO_User is not a valid username." |
|||
_err "Please check and retry." |
|||
return 1 |
|||
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" |
|||
|
|||
_debug "Getting txt records" |
|||
_uno_rest GET "my/products/$h/dns/records" |
|||
|
|||
if ! _contains "$response" "\"status\": 200"; then |
|||
_err "Error" |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _contains "$response" "$_sub_domain"; then |
|||
_info "Don't need to remove." |
|||
else |
|||
record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1) |
|||
record_line_number=$(_math "$record_line_number" - 1) |
|||
record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") |
|||
_debug "record_id" "$record_id" |
|||
|
|||
if [ -z "$record_id" ]; then |
|||
_err "Can not get record id to remove." |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then |
|||
_err "Delete record error." |
|||
return 1 |
|||
fi |
|||
_contains "$response" "\"status\": 200" |
|||
fi |
|||
|
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#_acme-challenge.www.domain.com |
|||
#returns |
|||
# _sub_domain=_acme-challenge.www |
|||
# _domain=domain.com |
|||
# _domain_id=sdjkglgdfewsdfg |
|||
_get_root() { |
|||
domain=$1 |
|||
i=2 |
|||
p=1 |
|||
while true; do |
|||
h=$(printf "%s" "$domain" | cut -d . -f $i-100) |
|||
_debug h "$h" |
|||
if [ -z "$h" ]; then |
|||
#not valid |
|||
return 1 |
|||
fi |
|||
|
|||
if ! _uno_rest GET "my/products/$h/dns/records"; then |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "\"status\": 200"; then |
|||
_domain_id=$h |
|||
if [ "$_domain_id" ]; then |
|||
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) |
|||
_domain=$h |
|||
return 0 |
|||
fi |
|||
return 1 |
|||
fi |
|||
p=$i |
|||
i=$(_math "$i" + 1) |
|||
done |
|||
return 1 |
|||
} |
|||
|
|||
_uno_rest() { |
|||
m=$1 |
|||
ep="$2" |
|||
data="$3" |
|||
_debug "$ep" |
|||
|
|||
export _H1="Content-Type: application/json" |
|||
|
|||
if [ "$m" != "GET" ]; then |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$Uno_Api/$UNO_User/$UNO_Key/$ep" "" "$m")" |
|||
else |
|||
response="$(_get "$Uno_Api/$UNO_User/$UNO_Key/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,149 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
#This is the vscale.io api wrapper for acme.sh |
|||
# |
|||
#Author: Alex Loban |
|||
#Report Bugs here: https://github.com/LAV45/acme.sh |
|||
|
|||
#VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
VSCALE_API_URL="https://api.vscale.io/v1" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_vscale_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
if [ -z "$VSCALE_API_KEY" ]; then |
|||
VSCALE_API_KEY="" |
|||
_err "You didn't specify the VSCALE api key yet." |
|||
_err "Please create you key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
_saveaccountconf VSCALE_API_KEY "$VSCALE_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" |
|||
|
|||
_vscale_tmpl_json="{\"type\":\"TXT\",\"name\":\"$_sub_domain.$_domain\",\"content\":\"$txtvalue\"}" |
|||
|
|||
if _vscale_rest POST "domains/$_domain_id/records/" "$_vscale_tmpl_json"; then |
|||
response=$(printf "%s\n" "$response" | _egrep_o "{\"error\": \".+\"" | cut -d : -f 2) |
|||
if [ -z "$response" ]; then |
|||
_info "txt record updated success." |
|||
return 0 |
|||
fi |
|||
fi |
|||
|
|||
return 1 |
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_vscale_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" |
|||
_vscale_rest GET "domains/$_domain_id/records/" |
|||
|
|||
if [ -n "$response" ]; then |
|||
record_id=$(printf "%s\n" "$response" | _egrep_o "\"TXT\", \"id\": [0-9]+, \"name\": \"$_sub_domain.$_domain\"" | cut -d : -f 2 | tr -d ", \"name\"") |
|||
_debug record_id "$record_id" |
|||
if [ -z "$record_id" ]; then |
|||
_err "Can not get record id to remove." |
|||
return 1 |
|||
fi |
|||
if _vscale_rest DELETE "domains/$_domain_id/records/$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 _vscale_rest GET "domains/"; 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 |
|||
_vscale_rest() { |
|||
mtd="$1" |
|||
ep="$2" |
|||
data="$3" |
|||
|
|||
_debug mtd "$mtd" |
|||
_debug ep "$ep" |
|||
|
|||
export _H1="Accept: application/json" |
|||
export _H2="Content-Type: application/json" |
|||
export _H3="X-Token: ${VSCALE_API_KEY}" |
|||
|
|||
if [ "$mtd" != "GET" ]; then |
|||
# both POST and DELETE. |
|||
_debug data "$data" |
|||
response="$(_post "$data" "$VSCALE_API_URL/$ep" "" "$mtd")" |
|||
else |
|||
response="$(_get "$VSCALE_API_URL/$ep")" |
|||
fi |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
_err "error $ep" |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
return 0 |
|||
} |
@ -0,0 +1,106 @@ |
|||
#!/usr/bin/env sh |
|||
# Author: non7top@gmail.com |
|||
# 07 Jul 2017 |
|||
# report bugs at https://github.com/non7top/acme.sh |
|||
|
|||
# Values to export: |
|||
# export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_yandex_add() { |
|||
fulldomain="${1}" |
|||
txtvalue="${2}" |
|||
_debug "Calling: dns_yandex_add() '${fulldomain}' '${txtvalue}'" |
|||
_PDD_credentials || return 1 |
|||
export _H1="PddToken: $PDD_Token" |
|||
|
|||
_PDD_get_domain "$fulldomain" |
|||
_debug "Found suitable domain in pdd: $curDomain" |
|||
curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}" |
|||
curUri="https://pddimp.yandex.ru/api2/admin/dns/add" |
|||
curResult="$(_post "${curData}" "${curUri}")" |
|||
_debug "Result: $curResult" |
|||
} |
|||
|
|||
#Usage: dns_myapi_rm _acme-challenge.www.domain.com |
|||
dns_yandex_rm() { |
|||
fulldomain="${1}" |
|||
_debug "Calling: dns_yandex_rm() '${fulldomain}'" |
|||
_PDD_credentials || return 1 |
|||
export _H1="PddToken: $PDD_Token" |
|||
record_id=$(pdd_get_record_id "${fulldomain}") |
|||
_debug "Result: $record_id" |
|||
|
|||
_PDD_get_domain "$fulldomain" |
|||
_debug "Found suitable domain in pdd: $curDomain" |
|||
|
|||
curUri="https://pddimp.yandex.ru/api2/admin/dns/del" |
|||
curData="domain=${curDomain}&record_id=${record_id}" |
|||
curResult="$(_post "${curData}" "${curUri}")" |
|||
_debug "Result: $curResult" |
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
|
|||
_PDD_get_domain() { |
|||
fulldomain="${1}" |
|||
__page=1 |
|||
__last=0 |
|||
while [ $__last -eq 0 ]; do |
|||
uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20" |
|||
res1=$(_get "$uri1" | _normalizeJson) |
|||
#_debug "$res1" |
|||
__found=$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p') |
|||
_debug "found: $__found results on page" |
|||
if [ "$__found" -lt 20 ]; then |
|||
_debug "last page: $__page" |
|||
__last=1 |
|||
fi |
|||
|
|||
__all_domains="$__all_domains $(echo "$res1" | tr "," "\n" | grep '"name"' | cut -d: -f2 | sed -e 's@"@@g')" |
|||
|
|||
__page=$(_math $__page + 1) |
|||
done |
|||
|
|||
k=2 |
|||
while [ $k -lt 10 ]; do |
|||
__t=$(echo "$fulldomain" | cut -d . -f $k-100) |
|||
_debug "finding zone for domain $__t" |
|||
for d in $__all_domains; do |
|||
if [ "$d" = "$__t" ]; then |
|||
p=$(_math $k - 1) |
|||
curSubdomain="$(echo "$fulldomain" | cut -d . -f "1-$p")" |
|||
curDomain="$__t" |
|||
return 0 |
|||
fi |
|||
done |
|||
k=$(_math $k + 1) |
|||
done |
|||
_err "No suitable domain found in your account" |
|||
return 1 |
|||
} |
|||
|
|||
_PDD_credentials() { |
|||
if [ -z "${PDD_Token}" ]; then |
|||
PDD_Token="" |
|||
_err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx" |
|||
_err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token" |
|||
return 1 |
|||
else |
|||
_saveaccountconf PDD_Token "${PDD_Token}" |
|||
fi |
|||
} |
|||
|
|||
pdd_get_record_id() { |
|||
fulldomain="${1}" |
|||
|
|||
_PDD_get_domain "$fulldomain" |
|||
_debug "Found suitable domain in pdd: $curDomain" |
|||
|
|||
curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}" |
|||
curResult="$(_get "${curUri}" | _normalizeJson)" |
|||
_debug "Result: $curResult" |
|||
echo "$curResult" | _egrep_o "{[^{]*\"content\":[^{]*\"subdomain\":\"${curSubdomain}\"" | sed -n -e 's#.* "record_id": \(.*\),[^,]*#\1#p' |
|||
} |
@ -0,0 +1,85 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
#ZM_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" |
|||
# |
|||
#https://zonomi.com dns api |
|||
|
|||
ZM_Api="https://zonomi.com/app/dns/dyndns.jsp" |
|||
|
|||
######## Public functions ##################### |
|||
|
|||
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" |
|||
dns_zonomi_add() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
ZM_Key="${ZM_Key:-$(_readaccountconf_mutable ZM_Key)}" |
|||
|
|||
if [ -z "$ZM_Key" ]; then |
|||
ZM_Key="" |
|||
_err "You don't specify zonomi api key yet." |
|||
_err "Please create your key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
#save the api key to the account conf file. |
|||
_saveaccountconf_mutable ZM_Key "$ZM_Key" |
|||
|
|||
_info "Get existing txt records for $fulldomain" |
|||
if ! _zm_request "action=QUERY&name=$fulldomain"; then |
|||
_err "error" |
|||
return 1 |
|||
fi |
|||
|
|||
if _contains "$response" "<record"; then |
|||
_debug "get and update records" |
|||
_qstr="action[1]=SET&type[1]=TXT&name[1]=$fulldomain&value[1]=$txtvalue" |
|||
_qindex=2 |
|||
for t in $(echo "$response" | tr -d "\r\n" | _egrep_o '<action.*</action>' | tr "<" "\n" | grep record | grep 'type="TXT"' | cut -d '"' -f 6); do |
|||
_debug2 t "$t" |
|||
_qstr="$_qstr&action[$_qindex]=SET&type[$_qindex]=TXT&name[$_qindex]=$fulldomain&value[$_qindex]=$t" |
|||
_qindex="$(_math "$_qindex" + 1)" |
|||
done |
|||
_zm_request "$_qstr" |
|||
else |
|||
_debug "Just add record" |
|||
_zm_request "action=SET&type=TXT&name=$fulldomain&value=$txtvalue" |
|||
fi |
|||
|
|||
} |
|||
|
|||
#fulldomain txtvalue |
|||
dns_zonomi_rm() { |
|||
fulldomain=$1 |
|||
txtvalue=$2 |
|||
|
|||
ZM_Key="${ZM_Key:-$(_readaccountconf_mutable ZM_Key)}" |
|||
if [ -z "$ZM_Key" ]; then |
|||
ZM_Key="" |
|||
_err "You don't specify zonomi api key yet." |
|||
_err "Please create your key and try again." |
|||
return 1 |
|||
fi |
|||
|
|||
_zm_request "action=DELETE&type=TXT&name=$fulldomain" |
|||
|
|||
} |
|||
|
|||
#################### Private functions below ################################## |
|||
#qstr |
|||
_zm_request() { |
|||
qstr="$1" |
|||
|
|||
_debug2 "qstr" "$qstr" |
|||
|
|||
_zm_url="$ZM_Api?api_key=$ZM_Key&$qstr" |
|||
_debug2 "_zm_url" "$_zm_url" |
|||
response="$(_get "$_zm_url")" |
|||
|
|||
if [ "$?" != "0" ]; then |
|||
return 1 |
|||
fi |
|||
_debug2 response "$response" |
|||
_contains "$response" "<is_ok>OK:" |
|||
} |
1288
le.sh
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue