Browse Source

Merge pull request #1 from acmesh-official/master

Update base from upstream
pull/1226/head
Evan Swendsen 5 years ago
committed by GitHub
parent
commit
903ae2954f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      .github/FUNDING.yml
  2. 8
      .github/ISSUE_TEMPLATE.md
  3. 8
      .github/PULL_REQUEST_TEMPLATE.md
  4. 40
      .github/auto-comment.yml
  5. 16
      .github/workflows/dockerhub.yml
  6. 14
      .travis.yml
  7. 9
      Dockerfile
  8. 674
      LICENSE.md
  9. 313
      README.md
  10. 2048
      acme.sh
  11. 116
      deploy/README.md
  12. 18
      deploy/cpanel_uapi.sh
  13. 287
      deploy/docker.sh
  14. 8
      deploy/fritzbox.sh
  15. 139
      deploy/gcore_cdn.sh
  16. 80
      deploy/gitlab.sh
  17. 267
      deploy/haproxy.sh
  18. 6
      deploy/keychain.sh
  19. 58
      deploy/mailcow.sh
  20. 59
      deploy/mydevil.sh
  21. 139
      deploy/panos.sh
  22. 96
      deploy/qiniu.sh
  23. 111
      deploy/routeros.sh
  24. 277
      deploy/ssh.sh
  25. 2
      deploy/strongswan.sh
  26. 155
      deploy/synology_dsm.sh
  27. 61
      deploy/vault_cli.sh
  28. 705
      dnsapi/README.md
  29. 254
      dnsapi/dns_1984hosting.sh
  30. 55
      dnsapi/dns_acmedns.sh
  31. 83
      dnsapi/dns_acmeproxy.sh
  32. 141
      dnsapi/dns_active24.sh
  33. 43
      dnsapi/dns_ali.sh
  34. 163
      dnsapi/dns_arvan.sh
  35. 142
      dnsapi/dns_aws.sh
  36. 348
      dnsapi/dns_azure.sh
  37. 114
      dnsapi/dns_cf.sh
  38. 197
      dnsapi/dns_clouddns.sh
  39. 64
      dnsapi/dns_cloudns.sh
  40. 157
      dnsapi/dns_cn.sh
  41. 253
      dnsapi/dns_conoha.sh
  42. 141
      dnsapi/dns_constellix.sh
  43. 54
      dnsapi/dns_cx.sh
  44. 2
      dnsapi/dns_cyon.sh
  45. 184
      dnsapi/dns_da.sh
  46. 130
      dnsapi/dns_ddnss.sh
  47. 204
      dnsapi/dns_desec.sh
  48. 65
      dnsapi/dns_df.sh
  49. 99
      dnsapi/dns_dgon.sh
  50. 35
      dnsapi/dns_dnsimple.sh
  51. 59
      dnsapi/dns_doapi.sh
  52. 155
      dnsapi/dns_domeneshop.sh
  53. 96
      dnsapi/dns_dp.sh
  54. 161
      dnsapi/dns_dpi.sh
  55. 97
      dnsapi/dns_dreamhost.sh
  56. 176
      dnsapi/dns_durabledns.sh
  57. 26
      dnsapi/dns_dynu.sh
  58. 121
      dnsapi/dns_dynv6.sh
  59. 171
      dnsapi/dns_easydns.sh
  60. 358
      dnsapi/dns_euserv.sh
  61. 168
      dnsapi/dns_exoscale.sh
  62. 331
      dnsapi/dns_freedns.sh
  63. 62
      dnsapi/dns_gandi_livedns.sh
  64. 167
      dnsapi/dns_gcloud.sh
  65. 79
      dnsapi/dns_gd.sh
  66. 168
      dnsapi/dns_gdnsdk.sh
  67. 116
      dnsapi/dns_he.sh
  68. 156
      dnsapi/dns_hexonet.sh
  69. 178
      dnsapi/dns_hostingde.sh
  70. 180
      dnsapi/dns_internetbs.sh
  71. 155
      dnsapi/dns_inwx.sh
  72. 4
      dnsapi/dns_ispconfig.sh
  73. 286
      dnsapi/dns_jd.sh
  74. 129
      dnsapi/dns_joker.sh
  75. 168
      dnsapi/dns_kas.sh
  76. 107
      dnsapi/dns_kinghost.sh
  77. 149
      dnsapi/dns_leaseweb.sh
  78. 67
      dnsapi/dns_lexicon.sh
  79. 186
      dnsapi/dns_linode_v4.sh
  80. 302
      dnsapi/dns_loopia.sh
  81. 40
      dnsapi/dns_lua.sh
  82. 69
      dnsapi/dns_maradns.sh
  83. 24
      dnsapi/dns_me.sh
  84. 210
      dnsapi/dns_miab.sh
  85. 159
      dnsapi/dns_misaka.sh
  86. 4
      dnsapi/dns_myapi.sh
  87. 97
      dnsapi/dns_mydevil.sh
  88. 210
      dnsapi/dns_mydnsjp.sh
  89. 407
      dnsapi/dns_namecheap.sh
  90. 86
      dnsapi/dns_namecom.sh
  91. 11
      dnsapi/dns_namesilo.sh
  92. 131
      dnsapi/dns_nederhost.sh
  93. 181
      dnsapi/dns_neodigit.sh
  94. 134
      dnsapi/dns_netcup.sh
  95. 205
      dnsapi/dns_nic.sh
  96. 88
      dnsapi/dns_nm.sh
  97. 64
      dnsapi/dns_nsd.sh
  98. 8
      dnsapi/dns_nsone.sh
  99. 54
      dnsapi/dns_nsupdate.sh
  100. 211
      dnsapi/dns_nw.sh

12
.github/FUNDING.yml

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: acmesh
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

8
.github/ISSUE_TEMPLATE.md

@ -1,6 +1,8 @@
<!-- <!--
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试输出,我帮不了你.
如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
我很忙, 每天可能只有 几秒钟 时间看你的 issue, 如果不按照我的要求写 issue, 你可能不会得到任何回复, 石沉大海.
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试信息. 我做不了什么.
如何调试 https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
If it is a bug report: If it is a bug report:
- make sure you are able to repro it on the latest released version. - make sure you are able to repro it on the latest released version.
@ -8,7 +10,7 @@ You can install the latest version by: `acme.sh --upgrade`
- Search the existing issues. - Search the existing issues.
- Refer to the [WIKI](https://wiki.acme.sh). - Refer to the [WIKI](https://wiki.acme.sh).
- Debug info [Debug](https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh).
- Debug info [Debug](https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh).
--> -->

8
.github/PULL_REQUEST_TEMPLATE.md

@ -1,9 +1,9 @@
<!-- <!--
Do NOT send pull request to `master` branch.
1. Do NOT send pull request to `master` branch.
Please send to `dev` branch instead. Please send to `dev` branch instead.
Any PR to `master` branch will NOT be merged. Any PR to `master` branch will NOT be merged.
2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
You will NOT get any review without passing this guide. You also need to fix the CI errors.
--> -->

40
.github/auto-comment.yml

@ -0,0 +1,40 @@
# Comment to a new issue.
issuesOpened: >
If this is a bug report, please upgrade to the latest code and try again:
如果有 bug, 请先更新到最新版试试:
```
acme.sh --upgrade
```
please also provide the log with `--debug 2`.
同时请提供调试输出 `--debug 2`
see: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
Without `--debug 2` log, your issue will NEVER get replied.
没有调试输出, 你的 issue 不会得到任何解答.
pullRequestOpened: >
First, NEVER send a PR to `master` branch, it will NEVER be accepted. Please send to the `dev` branch instead.
If this is a PR to support new DNS API or new notification API, please read this guide first:
https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
Please check the guide items one by one.
Then add your usage here:
https://github.com/acmesh-official/acme.sh/wiki/dnsapi
Or some other wiki pages:
https://github.com/acmesh-official/acme.sh/wiki/deployhooks
https://github.com/acmesh-official/acme.sh/wiki/notify

16
.github/workflows/dockerhub.yml

@ -0,0 +1,16 @@
name: Build DockerHub
on:
push:
branches: [ master, dev ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: trigger
run: curl -X POST https://hub.docker.com/api/build/v1/source/1813a660-2ee5-4583-a238-dd54e9a6ebac/trigger/c8cd9f1f-f269-45bc-9750-a08327257f62/call/

14
.travis.yml

@ -1,5 +1,4 @@
language: shell language: shell
sudo: required
dist: trusty dist: trusty
os: os:
@ -13,12 +12,6 @@ env:
global: global:
- SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64 - 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: install:
- if [ "$TRAVIS_OS_NAME" = 'osx' ]; then - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
@ -29,18 +22,15 @@ install:
script: script:
- echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)"
- command -V openssl && openssl version - 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 curl -sSL $SHFMT_URL -o ~/shfmt && chmod +x ~/shfmt && ~/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 git diff --exit-code && echo "shfmt OK" ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
- cd .. - cd ..
- git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
- git clone --depth 1 https://github.com/acmesh-official/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" = "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 - 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: matrix:
fast_finish: true fast_finish: true

9
Dockerfile

@ -1,10 +1,16 @@
FROM alpine:3.6
FROM alpine:3.10
RUN apk update -f \ RUN apk update -f \
&& apk --no-cache add -f \ && apk --no-cache add -f \
openssl \ openssl \
openssh-client \
coreutils \
bind-tools \
curl \ curl \
socat \ socat \
tzdata \
oath-toolkit-oathtool \
tar \
&& rm -rf /var/cache/apk/* && rm -rf /var/cache/apk/*
ENV LE_CONFIG_HOME /acme.sh ENV LE_CONFIG_HOME /acme.sh
@ -45,6 +51,7 @@ RUN for verb in help \
createCSR \ createCSR \
deactivate \ deactivate \
deactivate-account \ deactivate-account \
set-notify \
; do \ ; 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} \ 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 ; done

674
LICENSE.md

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

313
README.md

@ -1,8 +1,10 @@
# 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)
# An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/acmesh-official/acme.sh.svg?branch=master)](https://travis-ci.org/acmesh-official/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)
<a href="https://opencollective.com/acmesh" alt="Financial Contributors on Open Collective"><img src="https://opencollective.com/acmesh/all/badge.svg?label=financial+contributors" /></a> [![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. - An ACME protocol client written purely in Shell (Unix shell) language.
- Full ACME protocol implementation. - 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. - Simple, powerful and very easy to use. You only need 3 minutes to learn it.
- Bash, dash and sh compatible. - Bash, dash and sh compatible.
- Simplest shell script for Let's Encrypt free certificate client. - Simplest shell script for Let's Encrypt free certificate client.
@ -11,19 +13,20 @@
- DOES NOT require `root/sudoer` access. - DOES NOT require `root/sudoer` access.
- Docker friendly - Docker friendly
- IPv6 support - IPv6 support
- Cron job notifications for renewal or error etc.
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. 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
Wiki: https://github.com/acmesh-official/acme.sh/wiki
For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker)
For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker)
Twitter: [@neilpangxa](https://twitter.com/neilpangxa) Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
# [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
# [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
# Who are using **acme.sh**
# Who:
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
- [ruby-china.org](https://ruby-china.org/topics/31983) - [ruby-china.org](https://ruby-china.org/topics/31983)
- [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) - [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer))
@ -31,57 +34,67 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [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) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
- [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)
- [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
- [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh)
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) - [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)
- [CentOS Web Panel](http://centos-webpanel.com/)
- [lnmp.org](https://lnmp.org/)
- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
# Tested OS # Tested OS
| NO | Status| Platform| | 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
|1|[![](https://acmesh-official.github.io/acmetest/status/ubuntu-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Ubuntu
|2|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Debian
|3|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|CentOS
|4|[![](https://acmesh-official.github.io/acmetest/status/windows-cygwin.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
|5|[![](https://acmesh-official.github.io/acmetest/status/freebsd.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|FreeBSD
|6|[![](https://acmesh-official.github.io/acmetest/status/pfsense.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|pfsense
|7|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|openSUSE
|8|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Alpine Linux (with curl)
|9|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Archlinux
|10|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|fedora
|11|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Kali Linux
|12|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Oracle Linux
|13|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/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/acmesh-official/acme.sh/issues/111
|15|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|OpenBSD
|16|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Mageia
|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
|18|[![](https://acmesh-official.github.io/acmetest/status/solaris.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|SunOS/Solaris
|19|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Gentoo Linux
|20|[![Build Status](https://travis-ci.org/acmesh-official/acme.sh.svg?branch=master)](https://travis-ci.org/acmesh-official/acme.sh)|Mac OSX
|21|[![](https://acmesh-official.github.io/acmetest/status/clearlinux-latest.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)|ClearLinux
For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest):
https://github.com/acmesh-official/acmetest
# Supported CA
- Letsencrypt.org CA(default)
- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
# Supported modes # Supported modes
- Webroot mode - Webroot mode
- Standalone mode - Standalone mode
- Standalone tls-alpn mode
- Apache mode - Apache mode
- Nginx mode ( Beta )
- Nginx mode
- DNS mode - DNS mode
- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode)
- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode)
- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode)
# 1. How to install # 1. How to install
### 1. Install online ### 1. Install online
Check this project: https://github.com/Neilpang/get.acme.sh
Check this project: https://github.com/acmesh-official/get.acme.sh
```bash ```bash
curl https://get.acme.sh | sh curl https://get.acme.sh | sh
@ -99,14 +112,14 @@ wget -O - https://get.acme.sh | sh
Clone this project and launch installation: Clone this project and launch installation:
```bash ```bash
git clone https://github.com/Neilpang/acme.sh.git
git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh cd ./acme.sh
./acme.sh --install ./acme.sh --install
``` ```
You `don't have to be root` then, although `it is recommended`. You `don't have to be root` then, although `it is recommended`.
Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install
Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install
The installer will perform 3 actions: The installer will perform 3 actions:
@ -127,7 +140,7 @@ Ok, you are ready to issue certs now.
Show help message: Show help message:
```
```sh
root@v1:~# acme.sh -h root@v1:~# acme.sh -h
``` ```
@ -164,16 +177,16 @@ You must have at least one domain there.
You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`.
Generated/issued certs will be placed in `~/.acme.sh/example.com/`
The certs will be placed in `~/.acme.sh/example.com/`
The issued cert will be renewed automatically every **60** days.
The certs will be renewed automatically every **60** days.
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 3. Install the issued cert to Apache/Nginx etc.
# 3. Install the cert to Apache/Nginx etc.
After you issue a cert, you probably want to install/copy the cert to your Apache/Nginx or other servers.
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. 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.
**Apache** example: **Apache** example:
@ -195,13 +208,15 @@ acme.sh --install-cert -d example.com \
Only the domain is required, all the other parameters are optional. Only the domain is required, all the other parameters are optional.
The ownership and permission info of existing files are preserved. You may want to precreate the files to have defined ownership and permission.
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 issued cert/key to the production Apache or Nginx path.
Install/copy the cert/key to the production Apache or Nginx path.
The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`.
**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.**
# 4. Use Standalone server to issue cert # 4. Use Standalone server to issue cert
**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))**
@ -212,22 +227,19 @@ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to
acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
``` ```
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 5. Use Standalone TLS server to issue cert
# 5. Use Standalone ssl server to issue cert
**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** **(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. Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
```bash ```bash
acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com
acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com
``` ```
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 6. Use Apache mode # 6. Use Apache mode
@ -236,15 +248,19 @@ More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`.
Particularly, if you are running an Apache server, you should use Apache mode instead. This mode doesn't write any files to your web root folder.
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.
Just set string "apache" as the second argument and it will force use of apache plugin automatically. Just set string "apache" as the second argument and it will force use of apache plugin automatically.
```
```sh
acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
``` ```
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
**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 with your apache server, don't worry.**
More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 7. Use Nginx mode # 7. Use Nginx mode
@ -260,15 +276,31 @@ It will configure nginx server automatically to verify the domain and then resto
So, the config is not changed. So, the config is not changed.
```
```sh
acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com
``` ```
More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
**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 with your nginx server, don't worry.**
More examples: https://github.com/acmesh-official/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 most of the dns providers:
# 8. Use DNS mode:
https://github.com/acmesh-official/acme.sh/wiki/dnsapi
Support the `dns-01` challenge.
# 9. Use DNS manual mode:
See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.
If your dns provider doesn't support any api access, you can add the txt record by your hand.
```bash ```bash
acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
@ -276,7 +308,7 @@ acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
You should get an output like below: You should get an output like below:
```
```sh
Add the following txt record: Add the following txt record:
Domain:_acme-challenge.example.com Domain:_acme-challenge.example.com
Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c
@ -294,78 +326,19 @@ Then just rerun with `renew` argument:
acme.sh --renew -d example.com acme.sh --renew -d example.com
``` ```
Ok, it's finished.
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.** **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.** **Please use dns api mode instead.**
# 9. 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)
And:
1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
(DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)
**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)
# 10. Issue ECC certificates # 10. Issue ECC certificates
`Let's Encrypt` can now issue **ECDSA** certificates. `Let's Encrypt` can now issue **ECDSA** certificates.
And we support them too! And we support them too!
Just set the `length` parameter with a prefix `ec-`.
Just set the `keylength` parameter with a prefix `ec-`.
For example: For example:
@ -381,7 +354,7 @@ acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256
acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256
``` ```
Please look at the last parameter above.
Please look at the `keylength` parameter above.
Valid values are: Valid values are:
@ -390,36 +363,60 @@ Valid values are:
3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** 3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
# 11. How to renew the issued certs
No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
# 11. Issue Wildcard certificates
However, you can also force to renew any cert:
It's simple, just give a wildcard domain as the `-d` parameter.
```sh
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
``` ```
# 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.
However, you can also force to renew a cert:
```sh
acme.sh --renew -d example.com --force acme.sh --renew -d example.com --force
``` ```
or, for ECC cert: or, for ECC cert:
```
```sh
acme.sh --renew -d example.com --force --ecc acme.sh --renew -d example.com --force --ecc
``` ```
# 12. How to upgrade `acme.sh`
# 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. acme.sh is in constant development, so it's strongly recommended to use the latest code.
You can update acme.sh to the latest code: You can update acme.sh to the latest code:
```
```sh
acme.sh --upgrade acme.sh --upgrade
``` ```
You can also enable auto upgrade: You can also enable auto upgrade:
```
```sh
acme.sh --upgrade --auto-upgrade acme.sh --upgrade --auto-upgrade
``` ```
@ -427,42 +424,76 @@ Then **acme.sh** will be kept up to date automatically.
Disable auto upgrade: Disable auto upgrade:
```
```sh
acme.sh --upgrade --auto-upgrade 0 acme.sh --upgrade --auto-upgrade 0
``` ```
# 13. Issue a cert from an existing CSR
# 15. Issue a cert from an existing CSR
https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
# 16. Send notifications in cronjob
https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
https://github.com/acmesh-official/acme.sh/wiki/notify
# 14. Under the Hood
# 17. Under the Hood
Speak ACME language using shell, directly to "Let's Encrypt". Speak ACME language using shell, directly to "Let's Encrypt".
TODO: TODO:
# 15. Acknowledgments
# 18. Acknowledgments
1. Acme-tiny: https://github.com/diafygi/acme-tiny 1. Acme-tiny: https://github.com/diafygi/acme-tiny
2. ACME protocol: https://github.com/ietf-wg-acme/acme 2. ACME protocol: https://github.com/ietf-wg-acme/acme
3. Certbot: https://github.com/certbot/certbot
# 16. License & Others
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/acmesh-official/acme.sh/graphs/contributors"><img src="https://opencollective.com/acmesh/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)]
#### Individuals
<a href="https://opencollective.com/acmesh"><img src="https://opencollective.com/acmesh/individuals.svg?width=890"></a>
#### Organizations
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)]
<a href="https://opencollective.com/acmesh/organization/0/website"><img src="https://opencollective.com/acmesh/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/1/website"><img src="https://opencollective.com/acmesh/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/2/website"><img src="https://opencollective.com/acmesh/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/3/website"><img src="https://opencollective.com/acmesh/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/4/website"><img src="https://opencollective.com/acmesh/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/5/website"><img src="https://opencollective.com/acmesh/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/6/website"><img src="https://opencollective.com/acmesh/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/7/website"><img src="https://opencollective.com/acmesh/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
# 19. License & Others
License is GPLv3 License is GPLv3
Please Star and Fork me. Please Star and Fork me.
[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome.
[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
# 17. Donate
# 20. Donate
Your donation makes **acme.sh** better: Your donation makes **acme.sh** better:
1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)
[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list)
[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)

2048
acme.sh
File diff suppressed because it is too large
View File

116
deploy/README.md

@ -1,118 +1,6 @@
# Using deploy api # 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).
deploy hook usage:
Here are the scripts to deploy the certs/key to the server/services.
https://github.com/acmesh-official/acme.sh/wiki/deployhooks
## 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
(TODO)
## 4. Deploy the cert to local vsftpd server
```sh
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd
```
The default vsftpd conf file is `/etc/vsftpd.conf`, if your vsftpd conf is not in the default location, you can specify one:
```sh
export DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf"
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd
```
The default command to restart vsftpd server is `service vsftpd restart`, if it doesn't work, you can specify one:
```sh
export DEPLOY_VSFTPD_RELOAD="/etc/init.d/vsftpd restart"
acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd
```
## 5. Deploy the cert to local exim4 server
```sh
acme.sh --deploy -d ftp.example.com --deploy-hook exim4
```
The default exim4 conf file is `/etc/exim/exim.conf`, if your exim4 conf is not in the default location, you can specify one:
```sh
export DEPLOY_EXIM4_CONF="/etc/exim4/exim4.conf.template"
acme.sh --deploy -d ftp.example.com --deploy-hook exim4
```
The default command to restart exim4 server is `service exim4 restart`, if it doesn't work, you can specify one:
```sh
export DEPLOY_EXIM4_RELOAD="/etc/init.d/exim4 restart"
acme.sh --deploy -d ftp.example.com --deploy-hook exim4
```
## 6. Deploy the cert to OSX Keychain
```sh
acme.sh --deploy -d ftp.example.com --deploy-hook keychain
```
## 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 separete 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
```

18
deploy/cpanel_uapi.sh

@ -2,8 +2,12 @@
# Here is the script to deploy the cert to your cpanel using the cpanel API. # 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. # Uses command line uapi. --user option is needed only if run as root.
# Returns 0 when success. # Returns 0 when success.
# Written by Santeri Kannisto <santeri.kannisto@2globalnomads.info>
# Public domain, 2017
#
# Please note that I am no longer using Github. If you want to report an issue
# or contact me, visit https://forum.webseodesigners.com/web-design-seo-and-hosting-f16/
#
# Written by Santeri Kannisto <santeri.kannisto@webseodesigners.com>
# Public domain, 2017-2018
#export DEPLOY_CPANEL_USER=myusername #export DEPLOY_CPANEL_USER=myusername
@ -28,15 +32,9 @@ cpanel_uapi_deploy() {
_err "The command uapi is not found." _err "The command uapi is not found."
return 1 return 1
fi fi
if ! _exists php; then
_err "The command php is not found."
return 1
fi
# read cert and key files and urlencode both # 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\");")
_cert=$(_url_encode <"$_ccert")
_key=$(_url_encode <"$_ckey")
_debug _cert "$_cert" _debug _cert "$_cert"
_debug _key "$_key" _debug _key "$_key"

287
deploy/docker.sh

@ -0,0 +1,287 @@
#!/usr/bin/env sh
#DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx"
#DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem"
#DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem"
#DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem"
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
_DEPLOY_DOCKER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers"
_DOCKER_HOST_DEFAULT="/var/run/docker.sock"
docker_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL
_debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
if [ -z "$DEPLOY_DOCKER_CONTAINER_LABEL" ]; then
_err "The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container."
_err "See: $_DEPLOY_DOCKER_WIKI"
fi
_savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL"
if [ "$DOCKER_HOST" ]; then
_saveaccountconf DOCKER_HOST "$DOCKER_HOST"
fi
if _exists docker && docker version | grep -i docker >/dev/null; then
_info "Using docker command"
export _USE_DOCKER_COMMAND=1
else
export _USE_DOCKER_COMMAND=
fi
export _USE_UNIX_SOCKET=
if [ -z "$_USE_DOCKER_COMMAND" ]; then
export _USE_REST=
if [ "$DOCKER_HOST" ]; then
_debug "Try use docker host: $DOCKER_HOST"
export _USE_REST=1
else
export _DOCKER_SOCK="$_DOCKER_HOST_DEFAULT"
_debug "Try use $_DOCKER_SOCK"
if [ ! -e "$_DOCKER_SOCK" ] || [ ! -w "$_DOCKER_SOCK" ]; then
_err "$_DOCKER_SOCK is not available"
return 1
fi
export _USE_UNIX_SOCKET=1
if ! _exists "curl"; then
_err "Please install curl first."
_err "We need curl to work."
return 1
fi
if ! _check_curl_version; then
return 1
fi
fi
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
_debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"
fi
_getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
_debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
_savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
fi
_cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")"
_info "Container id: $_cid"
if [ -z "$_cid" ]; then
_err "can not find container id"
return 1
fi
if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then
if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then
return 1
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then
if ! _docker_cp "$_cid" "$_ccert" "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"; then
return 1
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then
if ! _docker_cp "$_cid" "$_cca" "$DEPLOY_DOCKER_CONTAINER_CA_FILE"; then
return 1
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then
if ! _docker_cp "$_cid" "$_cfullchain" "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"; then
return 1
fi
fi
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
_info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then
return 1
fi
fi
return 0
}
#label
_get_id() {
_label="$1"
if [ "$_USE_DOCKER_COMMAND" ]; then
docker ps -f label="$_label" --format "{{.ID}}"
elif [ "$_USE_REST" ]; then
_err "Not implemented yet."
return 1
elif [ "$_USE_UNIX_SOCKET" ]; then
_req="{\"label\":[\"$_label\"]}"
_debug2 _req "$_req"
_req="$(printf "%s" "$_req" | _url_encode)"
_debug2 _req "$_req"
listjson="$(_curl_unix_sock "${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}" GET "/containers/json?filters=$_req")"
_debug2 "listjson" "$listjson"
echo "$listjson" | tr '{,' '\n' | grep -i '"id":' | _head_n 1 | cut -d '"' -f 4
else
_err "Not implemented yet."
return 1
fi
}
#id cmd
_docker_exec() {
_eargs="$*"
_debug2 "_docker_exec $_eargs"
_dcid="$1"
shift
if [ "$_USE_DOCKER_COMMAND" ]; then
docker exec -i "$_dcid" sh -c "$*"
elif [ "$_USE_REST" ]; then
_err "Not implemented yet."
return 1
elif [ "$_USE_UNIX_SOCKET" ]; then
_cmd="$*"
#_cmd="$(printf "%s" "$_cmd" | sed 's/ /","/g')"
_debug2 _cmd "$_cmd"
#create exec instance:
cjson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/containers/$_dcid/exec" "{\"Cmd\": [\"sh\", \"-c\", \"$_cmd\"]}")"
_debug2 cjson "$cjson"
execid="$(echo "$cjson" | cut -d '"' -f 4)"
_debug execid "$execid"
ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")"
_debug2 ejson "$ejson"
if [ "$ejson" ]; then
_err "$ejson"
return 1
fi
else
_err "Not implemented yet."
return 1
fi
}
#id from to
_docker_cp() {
_dcid="$1"
_from="$2"
_to="$3"
_info "Copying file from $_from to $_to"
_dir="$(dirname "$_to")"
_debug2 _dir "$_dir"
if ! _docker_exec "$_dcid" mkdir -p "$_dir"; then
_err "Can not create dir: $_dir"
return 1
fi
if [ "$_USE_DOCKER_COMMAND" ]; then
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
_docker_exec "$_dcid" tee "$_to" <"$_from"
else
_docker_exec "$_dcid" tee "$_to" <"$_from" >/dev/null
fi
if [ "$?" = "0" ]; then
_info "Success"
return 0
else
_info "Error"
return 1
fi
elif [ "$_USE_REST" ]; then
_err "Not implemented yet."
return 1
elif [ "$_USE_UNIX_SOCKET" ]; then
_frompath="$_from"
if _startswith "$_frompath" '/'; then
_frompath="$(echo "$_from" | cut -b 2-)" #remove the first '/' char
fi
_debug2 "_frompath" "$_frompath"
_toname="$(basename "$_to")"
_debug2 "_toname" "$_toname"
_debug2 "_from" "$_from"
if ! tar --transform="s,$(printf "%s" "$_frompath" | tr '*' .),$_toname," -cz "$_from" 2>/dev/null | _curl_unix_sock "$_DOCKER_SOCK" PUT "/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf "%s" "$_dir" | _url_encode)" '@-' "Content-Type: application/octet-stream"; then
_err "copy error"
return 1
fi
return 0
else
_err "Not implemented yet."
return 1
fi
}
#sock method endpoint data content-type
_curl_unix_sock() {
_socket="$1"
_method="$2"
_endpoint="$3"
_data="$4"
_ctype="$5"
if [ -z "$_ctype" ]; then
_ctype="Content-Type: application/json"
fi
_debug _data "$_data"
_debug2 "url" "http://localhost$_endpoint"
if [ "$_CURL_NO_HOST" ]; then
_cux_url="http:$_endpoint"
else
_cux_url="http://localhost$_endpoint"
fi
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
curl -vvv --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
else
curl --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url"
fi
}
_check_curl_version() {
_cversion="$(curl -V | grep '^curl ' | cut -d ' ' -f 2)"
_debug2 "_cversion" "$_cversion"
_major="$(_getfield "$_cversion" 1 '.')"
_debug2 "_major" "$_major"
_minor="$(_getfield "$_cversion" 2 '.')"
_debug2 "_minor" "$_minor"
if [ "$_major$_minor" -lt "740" ]; then
_err "curl v$_cversion doesn't support unit socket"
return 1
fi
if [ "$_major$_minor" -lt "750" ]; then
_debug "Use short host name"
export _CURL_NO_HOST=1
else
export _CURL_NO_HOST=
fi
return 0
}

8
deploy/fritzbox.sh

@ -28,9 +28,11 @@ fritzbox_deploy() {
_debug _cfullchain "$_cfullchain" _debug _cfullchain "$_cfullchain"
if ! _exists iconv; then if ! _exists iconv; then
_err "iconv not found"
if ! _exists perl; then
_err "iconv or perl not found"
return 1 return 1
fi fi
fi
_fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}" _fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}"
_fritzbox_password="${DEPLOY_FRITZBOX_PASSWORD}" _fritzbox_password="${DEPLOY_FRITZBOX_PASSWORD}"
@ -61,7 +63,11 @@ fritzbox_deploy() {
_info "Log in to the FRITZ!Box" _info "Log in to the FRITZ!Box"
_fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')" _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')"
if _exists iconv; then
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')"
else
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | md5sum | awk '{print $1}')"
fi
_fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')" _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 if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then

139
deploy/gcore_cdn.sh

@ -0,0 +1,139 @@
#!/usr/bin/env sh
# Here is the script to deploy the cert to G-Core CDN service (https://gcorelabs.com/ru/) using the G-Core Labs API (https://docs.gcorelabs.com/cdn/).
# Returns 0 when success.
#
# Written by temoffey <temofffey@gmail.com>
# Public domain, 2019
#export DEPLOY_GCORE_CDN_USERNAME=myusername
#export DEPLOY_GCORE_CDN_PASSWORD=mypassword
######## Public functions #####################
#domain keyfile certfile cafile fullchain
gcore_cdn_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"
_fullchain=$(tr '\r\n' '*#' <"$_cfullchain" | sed 's/*#/#/g;s/##/#/g;s/#/\\n/g')
_key=$(tr '\r\n' '*#' <"$_ckey" | sed 's/*#/#/g;s/#/\\n/g')
_debug _fullchain "$_fullchain"
_debug _key "$_key"
if [ -z "$DEPLOY_GCORE_CDN_USERNAME" ]; then
if [ -z "$Le_Deploy_gcore_cdn_username" ]; then
_err "Please define the target username: export DEPLOY_GCORE_CDN_USERNAME=username"
return 1
fi
else
Le_Deploy_gcore_cdn_username="$DEPLOY_GCORE_CDN_USERNAME"
_savedomainconf Le_Deploy_gcore_cdn_username "$Le_Deploy_gcore_cdn_username"
fi
if [ -z "$DEPLOY_GCORE_CDN_PASSWORD" ]; then
if [ -z "$Le_Deploy_gcore_cdn_password" ]; then
_err "Please define the target password: export DEPLOY_GCORE_CDN_PASSWORD=password"
return 1
fi
else
Le_Deploy_gcore_cdn_password="$DEPLOY_GCORE_CDN_PASSWORD"
_savedomainconf Le_Deploy_gcore_cdn_password "$Le_Deploy_gcore_cdn_password"
fi
_info "Get authorization token"
_request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
_debug _request "$_request"
export _H1="Content-Type:application/json"
_response=$(_post "$_request" "https://api.gcdn.co/auth/signin")
_debug _response "$_response"
_regex=".*\"token\":\"\([-._0-9A-Za-z]*\)\".*$"
_debug _regex "$_regex"
_token=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _token "$_token"
if [ -z "$_token" ]; then
_err "Error G-Core Labs API authorization"
return 1
fi
_info "Find CDN resource with cname $_cdomain"
export _H2="Authorization:Token $_token"
_response=$(_get "https://api.gcdn.co/resources")
_debug _response "$_response"
_regex=".*(\"id\".*?\"cname\":\"$_cdomain\".*?})"
_regex="^.*\"cname\":\"$_cdomain\".*$"
_debug _regex "$_regex"
_resource=$(echo "$_response" | sed 's/},{/},\n{/g' | _egrep_o "$_regex")
_debug _resource "$_resource"
_regex=".*\"id\":\([0-9]*\).*\"rules\".*$"
_debug _regex "$_regex"
_resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _resourceId "$_resourceId"
_regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex"
_sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _sslDataOld "$_sslDataOld"
_regex=".*\"originGroup\":\([0-9]*\).*$"
_debug _regex "$_regex"
_originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _originGroup "$_originGroup"
if [ -z "$_resourceId" ] || [ -z "$_originGroup" ]; then
_err "Not found CDN resource with cname $_cdomain"
return 1
fi
_info "Add new SSL certificate"
_date=$(date "+%d.%m.%Y %H:%M:%S")
_request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}"
_debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/sslData")
_debug _response "$_response"
_regex=".*\"id\":\([0-9]*\).*$"
_debug _regex "$_regex"
_sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _sslDataAdd "$_sslDataAdd"
if [ -z "$_sslDataAdd" ]; then
_err "Error new SSL certificate add"
return 1
fi
_info "Update CDN resource"
_request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}"
_debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT")
_debug _response "$_response"
_regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex"
_sslDataNew=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _sslDataNew "$_sslDataNew"
if [ "$_sslDataNew" != "$_sslDataAdd" ]; then
_err "Error CDN resource update"
return 1
fi
if [ -z "$_sslDataOld" ] || [ "$_sslDataOld" = "null" ]; then
_info "Not found old SSL certificate"
else
_info "Delete old SSL certificate"
_response=$(_post '' "https://api.gcdn.co/sslData/$_sslDataOld" '' "DELETE")
_debug _response "$_response"
fi
_info "Certificate successfully deployed"
return 0
}

80
deploy/gitlab.sh

@ -0,0 +1,80 @@
#!/usr/bin/env sh
# Script to deploy certificate to a Gitlab hosted page
# The following variables exported from environment will be used.
# If not set then values previously saved in domain.conf file are used.
# All the variables are required
# export GITLAB_TOKEN="xxxxxxx"
# export GITLAB_PROJECT_ID=012345
# export GITLAB_DOMAIN="mydomain.com"
gitlab_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$GITLAB_TOKEN" ]; then
if [ -z "$Le_Deploy_gitlab_token" ]; then
_err "GITLAB_TOKEN not defined."
return 1
fi
else
Le_Deploy_gitlab_token="$GITLAB_TOKEN"
_savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token"
fi
if [ -z "$GITLAB_PROJECT_ID" ]; then
if [ -z "$Le_Deploy_gitlab_project_id" ]; then
_err "GITLAB_PROJECT_ID not defined."
return 1
fi
else
Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID"
_savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id"
fi
if [ -z "$GITLAB_DOMAIN" ]; then
if [ -z "$Le_Deploy_gitlab_domain" ]; then
_err "GITLAB_DOMAIN not defined."
return 1
fi
else
Le_Deploy_gitlab_domain="$GITLAB_DOMAIN"
_savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain"
fi
string_fullchain=$(_url_encode <"$_cfullchain")
string_key=$(_url_encode <"$_ckey")
body="certificate=$string_fullchain&key=$string_key"
export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token"
gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain"
_response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline")
error_response="error"
if test "${_response#*$error_response}" != "$_response"; then
_err "Error in deploying certificate:"
_err "$_response"
return 1
fi
_debug response "$_response"
_info "Certificate successfully deployed"
return 0
}

267
deploy/haproxy.sh

@ -1,8 +1,41 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#Here is a script to deploy cert to haproxy server.
#returns 0 means success, otherwise error.
# Script for acme.sh to deploy certificates to haproxy
#
# The following variables can be exported:
#
# export DEPLOY_HAPROXY_PEM_NAME="${domain}.pem"
#
# Defines the name of the PEM file.
# Defaults to "<domain>.pem"
#
# export DEPLOY_HAPROXY_PEM_PATH="/etc/haproxy"
#
# Defines location of PEM file for HAProxy.
# Defaults to /etc/haproxy
#
# export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy"
#
# OPTIONAL: Reload command used post deploy
# This defaults to be a no-op (ie "true").
# It is strongly recommended to set this something that makes sense
# for your distro.
#
# export DEPLOY_HAPROXY_ISSUER="no"
#
# OPTIONAL: Places CA file as "${DEPLOY_HAPROXY_PEM}.issuer"
# Note: Required for OCSP stapling to work
#
# export DEPLOY_HAPROXY_BUNDLE="no"
#
# OPTIONAL: Deploy this certificate as part of a multi-cert bundle
# This adds a suffix to the certificate based on the certificate type
# eg RSA certificates will have .rsa as a suffix to the file name
# HAProxy will load all certificates and provide one or the other
# depending on client capabilities
# Note: This functionality requires HAProxy was compiled against
# a version of OpenSSL that supports this.
#
######## Public functions ##################### ######## Public functions #####################
@ -14,13 +47,229 @@ haproxy_deploy() {
_cca="$4" _cca="$4"
_cfullchain="$5" _cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
# Some defaults
DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy"
DEPLOY_HAPROXY_PEM_NAME_DEFAULT="${_cdomain}.pem"
DEPLOY_HAPROXY_BUNDLE_DEFAULT="no"
DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
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}"
_err "deploy cert to haproxy server, Not implemented yet"
# PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then
Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
_savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}"
elif [ -z "${Le_Deploy_haproxy_pem_path}" ]; then
Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
fi
# Ensure PEM_PATH exists
if [ -d "${Le_Deploy_haproxy_pem_path}" ]; then
_debug "PEM_PATH ${Le_Deploy_haproxy_pem_path} exists"
else
_err "PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist"
return 1 return 1
fi
# PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}"
_savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then
Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
fi
# BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then
Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}"
_savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}"
elif [ -z "${Le_Deploy_haproxy_bundle}" ]; then
Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
fi
# ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then
Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}"
_savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}"
elif [ -z "${Le_Deploy_haproxy_issuer}" ]; then
Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
fi
# RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}"
_savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}"
elif [ -z "${Le_Deploy_haproxy_reload}" ]; then
Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
fi
# Set the suffix depending if we are creating a bundle or not
if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then
_info "Bundle creation requested"
# Initialise $Le_Keylength if its not already set
if [ -z "${Le_Keylength}" ]; then
Le_Keylength=""
fi
if _isEccKey "${Le_Keylength}"; then
_info "ECC key type detected"
_suffix=".ecdsa"
else
_info "RSA key type detected"
_suffix=".rsa"
fi
else
_suffix=""
fi
_debug _suffix "${_suffix}"
# Set variables for later
_pem="${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}"
_issuer="${_pem}.issuer"
_ocsp="${_pem}.ocsp"
_reload="${Le_Deploy_haproxy_reload}"
_info "Deploying PEM file"
# Create a temporary PEM file
_temppem="$(_mktemp)"
_debug _temppem "${_temppem}"
cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
_ret="$?"
# Check that we could create the temporary file
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} returned during PEM file creation"
[ -f "${_temppem}" ] && rm -f "${_temppem}"
return ${_ret}
fi
# Move PEM file into place
_info "Moving new certificate into place"
_debug _pem "${_pem}"
cat "${_temppem}" >"${_pem}"
_ret=$?
# Clean up temp file
[ -f "${_temppem}" ] && rm -f "${_temppem}"
# Deal with any failure of moving PEM file into place
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} returned while moving new certificate into place"
return ${_ret}
fi
# Update .issuer file if requested
if [ "${Le_Deploy_haproxy_issuer}" = "yes" ]; then
_info "Updating .issuer file"
_debug _issuer "${_issuer}"
cat "${_cca}" >"${_issuer}"
_ret="$?"
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} returned while copying issuer/CA certificate into place"
return ${_ret}
fi
else
[ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists"
fi
# Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
if [ -z "${Le_OCSP_Staple}" ]; then
Le_OCSP_Staple="0"
fi
if [ "${Le_OCSP_Staple}" = "1" ]; then
_info "Updating OCSP stapling info"
_debug _ocsp "${_ocsp}"
_info "Extracting OCSP URL"
_ocsp_url=$(openssl x509 -noout -ocsp_uri -in "${_pem}")
_debug _ocsp_url "${_ocsp_url}"
# Only process OCSP if URL was present
if [ "${_ocsp_url}" != "" ]; then
# Extract the hostname from the OCSP URL
_info "Extracting OCSP URL"
_ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
_debug _ocsp_host "${_ocsp_host}"
# Only process the certificate if we have a .issuer file
if [ -r "${_issuer}" ]; then
# Check if issuer cert is also a root CA cert
_subjectdn=$(openssl x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
_debug _subjectdn "${_subjectdn}"
_issuerdn=$(openssl x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
_debug _issuerdn "${_issuerdn}"
_info "Requesting OCSP response"
# If the issuer is a CA cert then our command line has "-CAfile" added
if [ "${_subjectdn}" = "${_issuerdn}" ]; then
_cafile_argument="-CAfile \"${_issuer}\""
else
_cafile_argument=""
fi
_debug _cafile_argument "${_cafile_argument}"
# if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
_openssl_version=$(openssl version | cut -d' ' -f2)
_debug _openssl_version "${_openssl_version}"
_openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
_openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
_header_sep="="
else
_header_sep=" "
fi
# Request the OCSP response from the issuer and store it
_openssl_ocsp_cmd="openssl ocsp \
-issuer \"${_issuer}\" \
-cert \"${_pem}\" \
-url \"${_ocsp_url}\" \
-header Host${_header_sep}\"${_ocsp_host}\" \
-respout \"${_ocsp}\" \
-verify_other \"${_issuer}\" \
${_cafile_argument} \
| grep -q \"${_pem}: good\""
_debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
eval "${_openssl_ocsp_cmd}"
_ret=$?
else
# Non fatal: No issuer file was present so no OCSP stapling file created
_err "OCSP stapling in use but no .issuer file was present"
fi
else
# Non fatal: No OCSP url was found int the certificate
_err "OCSP update requested but no OCSP URL was found in certificate"
fi
# Non fatal: Check return code of openssl command
if [ "${_ret}" != "0" ]; then
_err "Updating OCSP stapling failed with return code ${_ret}"
fi
else
# An OCSP file was already present but certificate did not have OCSP extension
if [ -f "${_ocsp}" ]; then
_err "OCSP was not requested but .ocsp file exists."
# Could remove the file at this step, although HAProxy just ignores it in this case
# rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
fi
fi
# Reload HAProxy
_debug _reload "${_reload}"
eval "${_reload}"
_ret=$?
if [ "${_ret}" != "0" ]; then
_err "Error code ${_ret} during reload"
return ${_ret}
else
_info "Reload successful"
fi
return 0
} }

6
deploy/keychain.sh

@ -1,11 +1,5 @@
#!/usr/bin/env sh #!/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 ##################### ######## Public functions #####################
#domain keyfile certfile cafile fullchain #domain keyfile certfile cafile fullchain

58
deploy/mailcow.sh

@ -0,0 +1,58 @@
#!/usr/bin/env sh
#Here is a script to deploy cert to mailcow.
#returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
mailcow_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"
_mailcow_path="${DEPLOY_MAILCOW_PATH}"
if [ -z "$_mailcow_path" ]; then
_err "Mailcow path is not found, please define DEPLOY_MAILCOW_PATH."
return 1
fi
_ssl_path="${_mailcow_path}/data/assets/ssl/"
if [ ! -d "$_ssl_path" ]; then
_err "Cannot find mailcow ssl path: $_ssl_path"
return 1
fi
_info "Copying key and cert"
_real_key="$_ssl_path/key.pem"
if ! cat "$_ckey" >"$_real_key"; then
_err "Error: write key file to: $_real_key"
return 1
fi
_real_fullchain="$_ssl_path/cert.pem"
if ! cat "$_cfullchain" >"$_real_fullchain"; then
_err "Error: write cert file to: $_real_fullchain"
return 1
fi
DEFAULT_MAILCOW_RELOAD="cd ${_mailcow_path} && docker-compose restart postfix-mailcow dovecot-mailcow nginx-mailcow"
_reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}"
_info "Run reload: $_reload"
if eval "$_reload"; then
_info "Reload success!"
fi
return 0
}

59
deploy/mydevil.sh

@ -0,0 +1,59 @@
#!/usr/bin/env sh
# MyDevil.net API (2019-02-03)
#
# MyDevil.net already supports automatic Let's Encrypt certificates,
# except for wildcard domains.
#
# This script depends on `devil` command that MyDevil.net provides,
# which means that it works only on server side.
#
# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
#
######## Public functions #####################
# Usage: mydevil_deploy domain keyfile certfile cafile fullchain
mydevil_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
ip=""
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if ! _exists "devil"; then
_err "Could not find 'devil' command."
return 1
fi
ip=$(mydevil_get_ip "$_cdomain")
if [ -z "$ip" ]; then
_err "Could not find IP for domain $_cdomain."
return 1
fi
# Delete old certificate first
_info "Removing old certificate for $_cdomain at $ip"
devil ssl www del "$ip" "$_cdomain"
# Add new certificate
_info "Adding new certificate for $_cdomain at $ip"
devil ssl www add "$ip" "$_cfullchain" "$_ckey" "$_cdomain" || return 1
return 0
}
#################### Private functions below ##################################
# Usage: ip=$(mydevil_get_ip domain.com)
# echo $ip
mydevil_get_ip() {
devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1
return 0
}

139
deploy/panos.sh

@ -0,0 +1,139 @@
#!/usr/bin/env sh
# Script to deploy certificates to Palo Alto Networks PANOS via API
# Note PANOS API KEY and IP address needs to be set prior to running.
# The following variables exported from environment will be used.
# If not set then values previously saved in domain.conf file are used.
#
# Firewall admin with superuser and IP address is required.
#
# export PANOS_USER="" # required
# export PANOS_PASS="" # required
# export PANOS_HOST="" # required
# This function is to parse the XML
parse_response() {
type=$2
if [ "$type" = 'keygen' ]; then
status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
if [ "$status" = "success" ]; then
panos_key=$(echo "$1" | sed 's/^.*\(<key>\)\(.*\)<\/key>.*/\2/g')
_panos_key=$panos_key
else
message="PAN-OS Key could not be set."
fi
else
status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g')
message=$(echo "$1" | sed 's/^.*<result>\(.*\)<\/result.*/\1/g')
fi
return 0
}
deployer() {
content=""
type=$1 # Types are keygen, cert, key, commit
_debug "**** Deploying $type *****"
panos_url="https://$_panos_host/api/"
if [ "$type" = 'keygen' ]; then
_H1="Content-Type: application/x-www-form-urlencoded"
content="type=keygen&user=$_panos_user&password=$_panos_pass"
# content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
fi
if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
#Generate DEIM
delim="-----MultipartDelimiter$(date "+%s%N")"
nl="\015\012"
#Set Header
export _H1="Content-Type: multipart/form-data; boundary=$delim"
if [ "$type" = 'cert' ]; then
panos_url="${panos_url}?type=import"
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
fi
if [ "$type" = 'key' ]; then
panos_url="${panos_url}?type=import"
content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
fi
#Close multipart
content="$content${nl}--$delim--${nl}${nl}"
#Convert CRLF
content=$(printf %b "$content")
fi
if [ "$type" = 'commit' ]; then
export _H1="Content-Type: application/x-www-form-urlencoded"
cmd=$(printf "%s" "<commit><partial><$_panos_user></$_panos_user></partial></commit>" | _url_encode)
content="type=commit&key=$_panos_key&cmd=$cmd"
fi
response=$(_post "$content" "$panos_url" "" "POST")
parse_response "$response" "$type"
# Saving response to variables
response_status=$status
#DEBUG
_debug response_status "$response_status"
if [ "$response_status" = "success" ]; then
_debug "Successfully deployed $type"
return 0
else
_err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
_debug "$message"
return 1
fi
}
# This is the main function that will call the other functions to deploy everything.
panos_deploy() {
_cdomain="$1"
_ckey="$2"
_cfullchain="$5"
# PANOS ENV VAR check
if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then
_debug "No ENV variables found lets check for saved variables"
_getdeployconf PANOS_USER
_getdeployconf PANOS_PASS
_getdeployconf PANOS_HOST
_panos_user=$PANOS_USER
_panos_pass=$PANOS_PASS
_panos_host=$PANOS_HOST
if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then
_err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs."
return 1
else
_debug "Using saved env variables."
fi
else
_debug "Detected ENV variables to be saved to the deploy conf."
# Encrypt and save user
_savedeployconf PANOS_USER "$PANOS_USER" 1
_savedeployconf PANOS_PASS "$PANOS_PASS" 1
_savedeployconf PANOS_HOST "$PANOS_HOST" 1
_panos_user="$PANOS_USER"
_panos_pass="$PANOS_PASS"
_panos_host="$PANOS_HOST"
fi
_debug "Let's use username and pass to generate token."
if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then
_err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST"
return 1
else
_debug "Getting PANOS KEY"
deployer keygen
if [ -z "$_panos_key" ]; then
_err "Missing apikey."
return 1
else
deployer cert
deployer key
deployer commit
fi
fi
}

96
deploy/qiniu.sh

@ -0,0 +1,96 @@
#!/usr/bin/env sh
# Script to create certificate to qiniu.com
#
# This deployment required following variables
# export QINIU_AK="QINIUACCESSKEY"
# export QINIU_SK="QINIUSECRETKEY"
# export QINIU_CDN_DOMAIN="cdn.example.com"
# If you have more than one domain, just
# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
QINIU_API_BASE="https://api.qiniu.com"
qiniu_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$QINIU_AK" ]; then
_err "QINIU_AK is not defined."
return 1
else
_savedomainconf QINIU_AK "$QINIU_AK"
fi
if [ -z "$QINIU_SK" ]; then
_err "QINIU_SK is not defined."
return 1
else
_savedomainconf QINIU_SK "$QINIU_SK"
fi
if [ "$QINIU_CDN_DOMAIN" ]; then
_savedomainconf QINIU_CDN_DOMAIN "$QINIU_CDN_DOMAIN"
else
QINIU_CDN_DOMAIN="$_cdomain"
fi
## upload certificate
string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')
string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')
sslcert_path="/sslcert"
sslcerl_body="{\"name\":\"$_cdomain\",\"common_name\":\"$QINIU_CDN_DOMAIN\",\"ca\":\"$string_fullchain\",\"pri\":\"$string_key\"}"
sslcert_access_token="$(_make_access_token "$sslcert_path")"
_debug sslcert_access_token "$sslcert_access_token"
export _H1="Authorization: QBox $sslcert_access_token"
sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64 "multiline")
if ! _contains "$sslcert_response" "certID"; then
_err "Error in creating certificate:"
_err "$sslcert_response"
return 1
fi
_debug sslcert_response "$sslcert_response"
_info "Certificate successfully uploaded, updating domain $_cdomain"
## extract certId
_certId="$(printf "%s" "$sslcert_response" | _normalizeJson | _egrep_o "certID\": *\"[^\"]*\"" | cut -d : -f 2)"
_debug certId "$_certId"
## update domain ssl config
update_body="{\"certid\":$_certId,\"forceHttps\":false}"
for domain in $QINIU_CDN_DOMAIN; do
update_path="/domain/$domain/httpsconf"
update_access_token="$(_make_access_token "$update_path")"
_debug update_access_token "$update_access_token"
export _H1="Authorization: QBox $update_access_token"
update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
if _contains "$update_response" "error"; then
_err "Error in updating domain $domain httpsconf:"
_err "$update_response"
return 1
fi
_debug update_response "$update_response"
_info "Domain $domain certificate has been deployed successfully"
done
return 0
}
_make_access_token() {
_token="$(printf "%s\n" "$1" | _hmac "sha1" "$(printf "%s" "$QINIU_SK" | _hex_dump | tr -d " ")" | _base64 | tr -- '+/' '-_')"
echo "$QINIU_AK:$_token"
}

111
deploy/routeros.sh

@ -0,0 +1,111 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to routeros router.
# Deploy the cert to remote routeros
#
# ```sh
# acme.sh --deploy -d ftp.example.com --deploy-hook routeros
# ```
#
# Before you can deploy the certificate to router os, you need
# to add the id_rsa.pub key to the routeros and assign a user
# to that key.
#
# The user need to have access to ssh, ftp, read and write.
#
# There are no need to enable ftp service for the script to work,
# as they are transmitted over SCP, however ftp is needed to store
# the files on the router.
#
# Then you need to set the environment variables for the
# deploy script to work.
#
# ```sh
# export ROUTER_OS_USERNAME=certuser
# export ROUTER_OS_HOST=router.example.com
#
# acme.sh --deploy -d ftp.example.com --deploy-hook routeros
# ```
#
# The deploy script will remove previously deployed certificates,
# and it does this with an assumption on how RouterOS names imported
# certificates, adding a "cer_0" suffix at the end. This is true for
# versions 6.32 -> 6.41.3, but it is not guaranteed that it will be
# true for future versions when upgrading.
#
# If the router have other certificates with the same name as the one
# beeing deployed, then this script will remove those certificates.
#
# At the end of the script, the services that use those certificates
# could be updated. Currently only the www-ssl service is beeing
# updated, but more services could be added.
#
# For instance:
# ```sh
# export ROUTER_OS_ADDITIONAL_SERVICES="/ip service set api-ssl certificate=$_cdomain.cer_0"
# ```
#
# One optional thing to do as well is to create a script that updates
# all the required services and run that script in a single command.
#
# returns 0 means success, otherwise error.
######## Public functions #####################
#domain keyfile certfile cafile fullchain
routeros_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_debug _cdomain "$_cdomain"
_debug _ckey "$_ckey"
_debug _ccert "$_ccert"
_debug _cca "$_cca"
_debug _cfullchain "$_cfullchain"
if [ -z "$ROUTER_OS_HOST" ]; then
_debug "Using _cdomain as ROUTER_OS_HOST, please set if not correct."
ROUTER_OS_HOST="$_cdomain"
fi
if [ -z "$ROUTER_OS_USERNAME" ]; then
_err "Need to set the env variable ROUTER_OS_USERNAME"
return 1
fi
if [ -z "$ROUTER_OS_ADDITIONAL_SERVICES" ]; then
_debug "Not enabling additional services"
ROUTER_OS_ADDITIONAL_SERVICES=""
fi
_info "Trying to push key '$_ckey' to router"
scp "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"
_info "Trying to push cert '$_cfullchain' to router"
scp "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer"
DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive \
source=\"## generated by routeros deploy script in acme.sh;\
\n/certificate remove [ find name=$_cdomain.cer_0 ];\
\n/certificate remove [ find name=$_cdomain.cer_1 ];\
\ndelay 1;\
\n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\
\n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\
\ndelay 1;\
\n/file remove $_cdomain.cer;\
\n/file remove $_cdomain.key;\
\ndelay 2;\
\n/ip service set www-ssl certificate=$_cdomain.cer_0;\
\n$ROUTER_OS_ADDITIONAL_SERVICES;\
\n\"
"
# shellcheck disable=SC2029
ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "$DEPLOY_SCRIPT_CMD"
# shellcheck disable=SC2029
ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script run \"LE Cert Deploy - $_cdomain\""
# shellcheck disable=SC2029
ssh "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "/system script remove \"LE Cert Deploy - $_cdomain\""
return 0
}

277
deploy/ssh.sh

@ -0,0 +1,277 @@
#!/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 -T"
# 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 or previously saved value
# export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
#
######## Public functions #####################
#domain keyfile certfile cafile fullchain
ssh_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_cfullchain="$5"
_err_code=0
_cmdstr=""
_backupprefix=""
_backupdir=""
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 -T"
fi
# BACKUP is optional. If not provided then default to previously saved value or yes.
if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then
Le_Deploy_ssh_backup="no"
elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
Le_Deploy_ssh_backup="yes"
fi
_savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup"
# BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then
Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH"
elif [ -z "$Le_Deploy_ssh_backup_path" ]; then
Le_Deploy_ssh_backup_path=".acme_ssh_deploy"
fi
_savedomainconf Le_Deploy_ssh_backup_path "$Le_Deploy_ssh_backup_path"
# MULTI_CALL is optional. If not provided then default to previously saved
# value (which may be undefined... equivalent to "no").
if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
Le_Deploy_ssh_multi_call="yes"
_savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call"
elif [ "$DEPLOY_SSH_MULTI_CALL" = "no" ]; then
Le_Deploy_ssh_multi_call=""
_cleardomainconf Le_Deploy_ssh_multi_call
fi
_info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
_info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
else
_info "Required commands batched and sent in single call to remote host"
fi
if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
_backupprefix="$Le_Deploy_ssh_backup_path/$_cdomain-backup"
_backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
# 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."
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# 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"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# CERTFILE is optional.
# If provided then certificate will be copied or appended to provided filename.
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
_savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
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"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
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"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
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"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
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"
if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
_cmdstr=""
fi
fi
# if commands not all sent in multiple calls then all commands sent in a single SSH call now...
if [ -n "$_cmdstr" ]; then
if ! _ssh_remote_cmd "$_cmdstr"; then
return $_err_code
fi
fi
return 0
}
#cmd
_ssh_remote_cmd() {
_cmd="$1"
_secure_debug "Remote commands to execute: $_cmd"
_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 "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmd'"
_err_code="$?"
if [ "$_err_code" != "0" ]; then
_err "Error code $_err_code returned from ssh"
fi
return $_err_code
}

2
deploy/strongswan.sh

@ -22,6 +22,8 @@ strongswan_deploy() {
_ipsec=/usr/sbin/ipsec _ipsec=/usr/sbin/ipsec
elif [ -x /usr/sbin/strongswan ]; then elif [ -x /usr/sbin/strongswan ]; then
_ipsec=/usr/sbin/strongswan _ipsec=/usr/sbin/strongswan
elif [ -x /usr/local/sbin/ipsec ]; then
_ipsec=/usr/local/sbin/ipsec
else else
_err "no strongswan or ipsec command is detected" _err "no strongswan or ipsec command is detected"
return 1 return 1

155
deploy/synology_dsm.sh

@ -0,0 +1,155 @@
#!/usr/bin/env sh
# Here is a script to deploy cert to Synology DSM
#
# it requires the jq and curl are in the $PATH and the following
# environment variables must be set:
#
# SYNO_Username - Synology Username to login (must be an administrator)
# SYNO_Password - Synology Password to login
# SYNO_Certificate - Certificate description to target for replacement
#
# The following environmental variables may be set if you don't like their
# default values:
#
# SYNO_Scheme - defaults to http
# SYNO_Hostname - defaults to localhost
# SYNO_Port - defaults to 5000
# SYNO_DID - device ID to skip OTP - defaults to empty
#
#returns 0 means success, otherwise error.
######## Public functions #####################
_syno_get_cookie_data() {
grep "\W$1=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';'
}
#domain keyfile certfile cafile fullchain
synology_dsm_deploy() {
_cdomain="$1"
_ckey="$2"
_ccert="$3"
_cca="$4"
_debug _cdomain "$_cdomain"
# Get Username and Password, but don't save until we successfully authenticate
_getdeployconf SYNO_Username
_getdeployconf SYNO_Password
_getdeployconf SYNO_Create
_getdeployconf SYNO_DID
if [ -z "$SYNO_Username" ] || [ -z "$SYNO_Password" ]; then
SYNO_Username=""
SYNO_Password=""
_err "SYNO_Username & SYNO_Password must be set"
return 1
fi
_debug2 SYNO_Username "$SYNO_Username"
_secure_debug2 SYNO_Password "$SYNO_Password"
# Optional scheme, hostname, and port for Synology DSM
_getdeployconf SYNO_Scheme
_getdeployconf SYNO_Hostname
_getdeployconf SYNO_Port
# default vaules for scheme, hostname, and port
# defaulting to localhost and http because it's localhost...
[ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http"
[ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost"
[ -n "${SYNO_Port}" ] || SYNO_Port="5000"
_savedeployconf SYNO_Scheme "$SYNO_Scheme"
_savedeployconf SYNO_Hostname "$SYNO_Hostname"
_savedeployconf SYNO_Port "$SYNO_Port"
_debug2 SYNO_Scheme "$SYNO_Scheme"
_debug2 SYNO_Hostname "$SYNO_Hostname"
_debug2 SYNO_Port "$SYNO_Port"
# Get the certificate description, but don't save it until we verfiy it's real
_getdeployconf SYNO_Certificate
if [ -z "${SYNO_Certificate:?}" ]; then
_err "SYNO_Certificate needs to be defined (with the Certificate description name)"
return 1
fi
_debug SYNO_Certificate "$SYNO_Certificate"
_base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port"
_debug _base_url "$_base_url"
# Login, get the token from JSON and session id from cookie
_info "Logging into $SYNO_Hostname:$SYNO_Port"
response=$(_get "$_base_url/webman/login.cgi?username=$SYNO_Username&passwd=$SYNO_Password&enable_syno_token=yes&device_id=$SYNO_DID")
token=$(echo "$response" | grep "SynoToken" | sed -n 's/.*"SynoToken" *: *"\([^"]*\).*/\1/p')
_debug3 response "$response"
if [ -z "$token" ]; then
_err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme."
_err "Check your username and password."
return 1
fi
_H1="Cookie: $(_syno_get_cookie_data "id"); $(_syno_get_cookie_data "smid")"
_H2="X-SYNO-TOKEN: $token"
export _H1
export _H2
_debug2 H1 "${_H1}"
_debug2 H2 "${_H2}"
# Now that we know the username and password are good, save them
_savedeployconf SYNO_Username "$SYNO_Username"
_savedeployconf SYNO_Password "$SYNO_Password"
_savedeployconf SYNO_DID "$SYNO_DID"
_debug token "$token"
_info "Getting certificates in Synology DSM"
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1" "$_base_url/webapi/entry.cgi")
_debug3 response "$response"
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\"id\":\"\([^\"]*\).*/\1/p")
_debug2 id "$id"
if [ -z "$id" ] && [ -z "${SYNO_Create:?}" ]; then
_err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set"
return 1
fi
# we've verified this certificate description is a thing, so save it
_savedeployconf SYNO_Certificate "$SYNO_Certificate"
default=false
if echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
default=true
fi
_debug2 default "$default"
_info "Generate form POST request"
nl="\015\012"
delim="--------------------------$(_utc_date | tr -d -- '-: ')"
content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\012"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}"
content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}${default}"
content="$content${nl}--$delim--${nl}"
content="$(printf "%b_" "$content")"
content="${content%_}" # protect trailing \n
_info "Upload certificate to the Synology DSM"
response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token" "" "POST" "multipart/form-data; boundary=${delim}")
_debug3 response "$response"
if ! echo "$response" | grep '"error":' >/dev/null; then
if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
_info "http services were restarted"
else
_info "http services were NOT restarted"
fi
return 0
else
_err "Unable to update certificate, error code $response"
return 1
fi
}

61
deploy/vault_cli.sh

@ -0,0 +1,61 @@
#!/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
if [ -n "$FABIO" ]; then
$VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
else
$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}/chain.pem" value=@"$_cca" || return 1
$VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
fi
}

705
dnsapi/README.md

@ -1,707 +1,6 @@
# How to use DNS API # How to use DNS API
DNS api usage:
## 1. Use CloudFlare domain API to automatically issue cert
First you need to login to your CloudFlare account to get your API key.
https://github.com/acmesh-official/acme.sh/wiki/dnsapi
```
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.
```
export DP_Id="1234"
export DP_Key="sADDsdasdgdsf"
```
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.
## 3. Use CloudXNS.com domain API to automatically issue cert
First you need to login to your CloudXNS account to get your API Key and Secret.
```
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
```
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 a cert now:
```
acme.sh --issue --dns dns_gd -d example.com -d www.example.com
```
The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 5. Use PowerDNS embedded API to automatically issue cert
First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration.
https://doc.powerdns.com/md/httpapi/README/
```
export PDNS_Url="http://ns.example.com:8081"
export PDNS_ServerId="localhost"
export PDNS_Token="0123456789ABCDEF"
export PDNS_Ttl=60
```
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 acme_key algorithm hmac-sha512 > /etc/knot/acme.key
```
Include this key in your knot configuration file.
```
include: /etc/knot/acme.key
```
Next, configure your zone to allow dynamic updates.
Dynamic updates for the zone are allowed via proper ACL rule with the `update` action. For in-depth instructions, please see [Knot DNS's documentation](https://www.knot-dns.cz/documentation/).
```
acl:
- id: acme_acl
address: 192.168.1.0/24
key: acme_key
action: update
zone:
- domain: example.com
file: example.com.zone
acl: acme_acl
```
Finally, make the DNS server and TSIG Key available to `acme.sh`
```
export KNOT_SERVER="dns.example.com"
export KNOT_KEY=`grep \# /etc/knot/acme.key | cut -d' ' -f2`
```
Ok, let's issue a cert now:
```
acme.sh --issue --dns dns_knot -d example.com -d www.example.com
```
The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 20. Use DigitalOcean API (native)
You need to obtain a read and write capable API key from your DigitalOcean account. See: https://www.digitalocean.com/help/api/
```
export DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc"
```
Ok, let's issue a cert now:
```
acme.sh --issue --dns dns_dgon -d example.com -d www.example.com
```
## 21. Use ClouDNS.net API
You need to set the HTTP API user ID and password credentials. See: https://www.cloudns.net/wiki/article/42/
```
export CLOUDNS_AUTH_ID=XXXXX
export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY"
```
Ok, let's issue a cert now:
```
acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com
```
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.
```
acme.sh --issue --dns dns_dnsimple -d example.com
```
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
```
## 27. Use DuckDNS.org API
```
export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
```
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
```
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 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:
```
acme.sh --issue --dns dns_unoeuro -d example.com -d www.example.com
```
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.
# 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

254
dnsapi/dns_1984hosting.sh

@ -0,0 +1,254 @@
#!/usr/bin/env sh
#This file name is "dns_1984hosting.sh"
#So, here must be a method dns_1984hosting_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: Adrian Fedoreanu
#Report Bugs here: https://github.com/acmesh-official/acme.sh
# or here... https://github.com/acmesh-official/acme.sh/issues/2851
#
######## Public functions #####################
# Export 1984HOSTING username and password in following variables
#
# One984HOSTING_Username=username
# One984HOSTING_Password=password
#
# sessionid cookie is saved in ~/.acme.sh/account.conf
# username/password need to be set only when changed.
#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_1984hosting_add() {
fulldomain=$1
txtvalue=$2
_info "Add TXT record using 1984Hosting"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _1984hosting_login; then
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_1984hosting_add_txt_record "$_domain" "$_sub_domain" "$txtvalue"
return $?
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_1984hosting_rm() {
fulldomain=$1
txtvalue=$2
_info "Delete TXT record using 1984Hosting"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _1984hosting_login; then
_err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_1984hosting_delete_txt_record "$_domain" "$_sub_domain"
return $?
}
#################### Private functions below ##################################
# usage _1984hosting_add_txt_record domain subdomain value
# returns 0 success
_1984hosting_add_txt_record() {
_debug "Add TXT record $1 with value '$3'"
domain="$1"
subdomain="$2"
value="$(printf '%s' "$3" | _url_encode)"
url="https://management.1984hosting.com/domains/entry/"
postdata="entry=new"
postdata="$postdata&type=TXT"
postdata="$postdata&ttl=3600"
postdata="$postdata&zone=$domain"
postdata="$postdata&host=$subdomain"
postdata="$postdata&rdata=%22$value%22"
_debug2 postdata "$postdata"
_authpost "$postdata" "$url"
response="$(echo "$_response" | _normalizeJson)"
_debug2 response "$response"
if _contains "$response" '"haserrors": true'; then
_err "1984Hosting failed to add TXT record for $subdomain bad RC from _post"
return 1
elif _contains "$response" "<html>"; then
_err "1984Hosting failed to add TXT record for $subdomain. Check $HTTP_HEADER file"
return 1
elif [ "$response" = '{"auth": false, "ok": false}' ]; then
_err "1984Hosting failed to add TXT record for $subdomain. Invalid or expired cookie"
return 1
fi
_info "Added acme challenge TXT record for $fulldomain at 1984Hosting"
return 0
}
# usage _1984hosting_delete_txt_record entry_id
# returns 0 success
_1984hosting_delete_txt_record() {
_debug "Delete $fulldomain TXT record"
domain="$1"
subdomain="$2"
url="https://management.1984hosting.com/domains"
_htmlget "$url" "$domain"
_debug2 _response "$_response"
zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+')"
_debug2 zone_id "$zone_id"
if [ -z "$zone_id" ]; then
_err "Error getting zone_id for $1"
return 1
fi
_htmlget "$url/$zone_id" "$subdomain"
_debug2 _response "$_response"
entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')"
_debug2 entry_id "$entry_id"
if [ -z "$entry_id" ]; then
_err "Error getting TXT entry_id for $1"
return 1
fi
_authpost "entry=$entry_id" "$url/delentry/"
response="$(echo "$_response" | _normalizeJson)"
_debug2 response "$response"
if ! _contains "$response" '"ok": true'; then
_err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post"
return 1
fi
_info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting"
return 0
}
# usage: _1984hosting_login username password
# returns 0 success
_1984hosting_login() {
if ! _check_credentials; then return 1; fi
if _check_cookie; then
_debug "Already logged in"
return 0
fi
_debug "Login to 1984Hosting as user $One984HOSTING_Username"
username=$(printf '%s' "$One984HOSTING_Username" | _url_encode)
password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
url="https://management.1984hosting.com/accounts/checkuserauth/"
response="$(_post "username=$username&password=$password&otpkey=" "$url")"
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
if [ "$response" = '{"loggedin": true, "ok": true}' ]; then
One984HOSTING_COOKIE="$(grep '^Set-Cookie:' "$HTTP_HEADER" | _tail_n 1 | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
export One984HOSTING_COOKIE
_saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
return 0
fi
return 1
}
_check_credentials() {
if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
One984HOSTING_Username=""
One984HOSTING_Password=""
_err "You haven't specified 1984Hosting username or password yet."
_err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again."
return 1
fi
return 0
}
_check_cookie() {
One984HOSTING_COOKIE="${One984HOSTING_COOKIE:-$(_readaccountconf_mutable One984HOSTING_COOKIE)}"
if [ -z "$One984HOSTING_COOKIE" ]; then
_debug "No cached cookie found"
return 1
fi
_authget "https://management.1984hosting.com/accounts/loginstatus/"
response="$(echo "$_response" | _normalizeJson)"
if [ "$_response" = '{"ok": true}' ]; then
_debug "Cached cookie still valid"
return 0
fi
_debug "Cached cookie no longer valid"
One984HOSTING_COOKIE=""
_saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
return 1
}
#_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
_authget "https://management.1984hosting.com/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
if _contains "$_response" "serial"; 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
}
# add extra headers to request
_authget() {
export _H1="Cookie: $One984HOSTING_COOKIE"
_response=$(_get "$1")
}
# truncate huge HTML response
# echo: Argument list too long
_htmlget() {
export _H1="Cookie: $One984HOSTING_COOKIE"
_response=$(_get "$1" | grep "$2" | _head_n 1)
}
# add extra headers to request
_authpost() {
export _H1="Cookie: $One984HOSTING_COOKIE"
_response=$(_post "$1" "$2")
}

55
dnsapi/dns_acmedns.sh

@ -0,0 +1,55 @@
#!/usr/bin/env sh
#
#Author: Wolfgang Ebner
#Report Bugs here: https://github.com/webner/acme.sh
#
######## Public functions #####################
#Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_acmedns_add() {
fulldomain=$1
txtvalue=$2
_info "Using acme-dns"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
ACMEDNS_UPDATE_URL="${ACMEDNS_UPDATE_URL:-$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)}"
ACMEDNS_USERNAME="${ACMEDNS_USERNAME:-$(_readaccountconf_mutable ACMEDNS_USERNAME)}"
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readaccountconf_mutable ACMEDNS_PASSWORD)}"
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readaccountconf_mutable ACMEDNS_SUBDOMAIN)}"
if [ "$ACMEDNS_UPDATE_URL" = "" ]; then
ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update"
fi
_saveaccountconf_mutable ACMEDNS_UPDATE_URL "$ACMEDNS_UPDATE_URL"
_saveaccountconf_mutable ACMEDNS_USERNAME "$ACMEDNS_USERNAME"
_saveaccountconf_mutable ACMEDNS_PASSWORD "$ACMEDNS_PASSWORD"
_saveaccountconf_mutable ACMEDNS_SUBDOMAIN "$ACMEDNS_SUBDOMAIN"
export _H1="X-Api-User: $ACMEDNS_USERNAME"
export _H2="X-Api-Key: $ACMEDNS_PASSWORD"
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
_debug data "$data"
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
_debug response "$response"
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
_err "invalid response of acme-dns"
return 1
fi
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_acmedns_rm() {
fulldomain=$1
txtvalue=$2
_info "Using acme-dns"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
}
#################### Private functions below ##################################

83
dnsapi/dns_acmeproxy.sh

@ -0,0 +1,83 @@
#!/usr/bin/env sh
## Acmeproxy DNS provider to be used with acmeproxy (http://github.com/mdbraber/acmeproxy)
## API integration by Maarten den Braber
##
## Report any bugs via https://github.com/mdbraber/acme.sh
dns_acmeproxy_add() {
fulldomain="${1}"
txtvalue="${2}"
action="present"
_debug "Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'"
_acmeproxy_request "$fulldomain" "$txtvalue" "$action"
}
dns_acmeproxy_rm() {
fulldomain="${1}"
txtvalue="${2}"
action="cleanup"
_debug "Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'"
_acmeproxy_request "$fulldomain" "$txtvalue" "$action"
}
_acmeproxy_request() {
## Nothing to see here, just some housekeeping
fulldomain=$1
txtvalue=$2
action=$3
_info "Using acmeproxy"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
ACMEPROXY_ENDPOINT="${ACMEPROXY_ENDPOINT:-$(_readaccountconf_mutable ACMEPROXY_ENDPOINT)}"
ACMEPROXY_USERNAME="${ACMEPROXY_USERNAME:-$(_readaccountconf_mutable ACMEPROXY_USERNAME)}"
ACMEPROXY_PASSWORD="${ACMEPROXY_PASSWORD:-$(_readaccountconf_mutable ACMEPROXY_PASSWORD)}"
## Check for the endpoint
if [ -z "$ACMEPROXY_ENDPOINT" ]; then
ACMEPROXY_ENDPOINT=""
_err "You didn't specify the endpoint"
_err "Please set them via 'export ACMEPROXY_ENDPOINT=https://ip:port' and try again."
return 1
fi
## Save the credentials to the account file
_saveaccountconf_mutable ACMEPROXY_ENDPOINT "$ACMEPROXY_ENDPOINT"
_saveaccountconf_mutable ACMEPROXY_USERNAME "$ACMEPROXY_USERNAME"
_saveaccountconf_mutable ACMEPROXY_PASSWORD "$ACMEPROXY_PASSWORD"
if [ -z "$ACMEPROXY_USERNAME" ] || [ -z "$ACMEPROXY_PASSWORD" ]; then
_info "ACMEPROXY_USERNAME and/or ACMEPROXY_PASSWORD not set - using without client authentication! Make sure you're using server authentication (e.g. IP-based)"
export _H1="Accept: application/json"
export _H2="Content-Type: application/json"
else
## Base64 encode the credentials
credentials=$(printf "%b" "$ACMEPROXY_USERNAME:$ACMEPROXY_PASSWORD" | _base64)
## Construct the HTTP Authorization header
export _H1="Authorization: Basic $credentials"
export _H2="Accept: application/json"
export _H3="Content-Type: application/json"
fi
## Add the challenge record to the acmeproxy grid member
response="$(_post "{\"fqdn\": \"$fulldomain.\", \"value\": \"$txtvalue\"}" "$ACMEPROXY_ENDPOINT/$action" "" "POST")"
## Let's see if we get something intelligible back from the unit
if echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
_info "Successfully updated the txt record"
return 0
else
_err "Error encountered during record addition"
_err "$response"
return 1
fi
}
#################### Private functions below ##################################

141
dnsapi/dns_active24.sh

@ -0,0 +1,141 @@
#!/usr/bin/env sh
#ACTIVE24_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
ACTIVE24_Api="https://api.active24.com"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_active24_add() {
fulldomain=$1
txtvalue=$2
_active24_init
_info "Adding txt record"
if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then
if _contains "$response" "errors"; then
_err "Add txt record error."
return 1
else
_info "Added, OK"
return 0
fi
fi
_err "Add txt record error."
return 1
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_active24_rm() {
fulldomain=$1
txtvalue=$2
_active24_init
_debug "Getting txt records"
_active24_rest GET "dns/$_domain/records/v1"
if _contains "$response" "errors"; then
_err "Error"
return 1
fi
hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-)
for hash_id in $hash_ids; do
_debug "Removing hash_id" "$hash_id"
if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then
if _contains "$response" "errors"; then
_err "Unable to remove txt record."
return 1
else
_info "Removed txt record."
return 0
fi
fi
done
_err "No txt records found."
return 1
}
#################### 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 ! _active24_rest GET "dns/domains/v1"; 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" "\"$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
return 1
}
_active24_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Authorization: Bearer $ACTIVE24_Token"
if [ "$m" != "GET" ]; then
_debug "data" "$data"
response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")"
else
response="$(_get "$ACTIVE24_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}
_active24_init() {
ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}"
if [ -z "$ACTIVE24_Token" ]; then
ACTIVE24_Token=""
_err "You didn't specify a Active24 api token yet."
_err "Please create the token and try again."
return 1
fi
_saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token"
_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"
}

43
dnsapi/dns_ali.sh

@ -10,6 +10,8 @@ dns_ali_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
Ali_Key="" Ali_Key=""
Ali_Secret="" Ali_Secret=""
@ -18,8 +20,8 @@ dns_ali_add() {
fi fi
#save the api key and secret to the account conf file. #save the api key and secret to the account conf file.
_saveaccountconf Ali_Key "$Ali_Key"
_saveaccountconf Ali_Secret "$Ali_Secret"
_saveaccountconf_mutable Ali_Key "$Ali_Key"
_saveaccountconf_mutable Ali_Secret "$Ali_Secret"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -32,6 +34,15 @@ dns_ali_add() {
dns_ali_rm() { dns_ali_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
return 1
fi
_clean _clean
} }
@ -76,16 +87,14 @@ _ali_rest() {
return 1 return 1
fi fi
_debug2 response "$response"
if [ -z "$2" ]; then if [ -z "$2" ]; then
message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ -n "$message" ]; then
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ "$message" ]; then
_err "$message" _err "$message"
return 1 return 1
fi fi
fi fi
_debug2 response "$response"
return 0
} }
_ali_urlencode() { _ali_urlencode() {
@ -112,12 +121,14 @@ _ali_nonce() {
} }
_check_exist_query() { _check_exist_query() {
_qdomain="$1"
_qsubdomain="$2"
query='' query=''
query=$query'AccessKeyId='$Ali_Key query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DescribeDomainRecords' query=$query'&Action=DescribeDomainRecords'
query=$query'&DomainName='$1
query=$query'&DomainName='$_qdomain
query=$query'&Format=json' query=$query'&Format=json'
query=$query'&RRKeyWord=_acme-challenge'
query=$query'&RRKeyWord='$_qsubdomain
query=$query'&SignatureMethod=HMAC-SHA1' query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)" query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0' query=$query'&SignatureVersion=1.0'
@ -169,17 +180,21 @@ _describe_records_query() {
} }
_clean() { _clean() {
_check_exist_query "$_domain"
_check_exist_query "$_domain" "$_sub_domain"
if ! _ali_rest "Check exist records" "ignore"; then if ! _ali_rest "Check exist records" "ignore"; then
return 1 return 1
fi fi
records="$(echo "$response" -n | _egrep_o "\"RecordId\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
printf "%s" "$records" \
| while read -r record_id; do
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" _delete_record_query "$record_id"
_ali_rest "Delete record $record_id" "ignore" _ali_rest "Delete record $record_id" "ignore"
done
fi
} }
_timestamp() { _timestamp() {

163
dnsapi/dns_arvan.sh

@ -0,0 +1,163 @@
#!/usr/bin/env sh
#Arvan_Token="xxxx"
ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains"
#Author: Ehsan Aliakbar
#Report Bugs here: https://github.com/Neilpang/acme.sh
#
######## Public functions #####################
#Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_arvan_add() {
fulldomain=$1
txtvalue=$2
_info "Using Arvan"
Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}"
if [ -z "$Arvan_Token" ]; then
_err "You didn't specify \"Arvan_Token\" token yet."
_err "You can get yours from here https://npanel.arvancloud.com/profile/api-keys"
return 1
fi
#save the api token to the account conf file.
_saveaccountconf_mutable Arvan_Token "$Arvan_Token"
_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 _arvan_rest POST "$_domain/dns-records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":{\"text\":\"$txtvalue\"},\"ttl\":120}"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
elif _contains "$response" "Record Data is Duplicated"; then
_info "Already exists, 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_arvan_rm() {
fulldomain=$1
txtvalue=$2
_info "Using Arvan"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}"
_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"
shorted_txtvalue=$(printf "%s" "$txtvalue" | cut -d "-" -d "_" -f1)
_arvan_rest GET "${_domain}/dns-records?search=$shorted_txtvalue"
if ! printf "%s" "$response" | grep \"current_page\":1 >/dev/null; then
_err "Error on Arvan Api"
_err "Please create a github issue with debbug log"
return 1
fi
count=$(printf "%s\n" "$response" | _egrep_o "\"total\":[^,]*" | 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 ! _arvan_rest "DELETE" "${_domain}/dns-records/$record_id"; then
_err "Delete record error."
return 1
fi
_debug "$response"
_contains "$response" 'dns record deleted'
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=1
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 ! _arvan_rest GET "?search=$h"; then
return 1
fi
if _contains "$response" "\"domain\":\"$h\"" || _contains "$response" '"total":1'; then
_domain_id=$(echo "$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
}
_arvan_rest() {
mtd="$1"
ep="$2"
data="$3"
token_trimmed=$(echo "$Arvan_Token" | tr -d '"')
export _H1="Authorization: $token_trimmed"
if [ "$mtd" = "DELETE" ]; then
#DELETE Request shouldn't have Content-Type
_debug data "$data"
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
elif [ "$mtd" = "POST" ]; then
export _H2="Content-Type: application/json"
_debug data "$data"
response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
else
response="$(_get "$ARVAN_API_URL/$ep$data")"
fi
}

142
dnsapi/dns_aws.sh

@ -6,11 +6,13 @@
#AWS_SECRET_ACCESS_KEY="xxxxxxx" #AWS_SECRET_ACCESS_KEY="xxxxxxx"
#This is the Amazon Route53 api wrapper for acme.sh #This is the Amazon Route53 api wrapper for acme.sh
#All `_sleep` commands are included to avoid Route53 throttling, see
#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
AWS_HOST="route53.amazonaws.com" AWS_HOST="route53.amazonaws.com"
AWS_URL="https://$AWS_HOST" AWS_URL="https://$AWS_HOST"
AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API"
AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
######## Public functions ##################### ######## Public functions #####################
@ -21,34 +23,72 @@ dns_aws_add() {
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
_use_container_role || _use_instance_role
fi
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
AWS_ACCESS_KEY_ID="" AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY="" AWS_SECRET_ACCESS_KEY=""
_err "You don't specify aws route53 api key id and and api key secret yet."
_err "Please create you key and try again. see $(__green $AWS_WIKI)"
_err "You haven't specifed the aws route53 api key id and and api key secret yet."
_err "Please create your key and try again. see $(__green $AWS_WIKI)"
return 1 return 1
fi fi
#save for future use
#save for future use, unless using a role which will be fetched as needed
if [ -z "$_using_role" ]; then
_saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
_saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
_saveaccountconf_mutable AWS_DNS_SLOWRATE "$AWS_DNS_SLOWRATE"
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" _err "invalid domain"
_sleep 1
return 1 return 1
fi fi
_debug _domain_id "$_domain_id" _debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords><ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
_info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
_sleep 1
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 aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
_info "txt record updated success."
if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
_info "The TXT record already exists. Skipping."
_sleep 1
return 0 return 0
fi 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 successfully."
if [ -n "$AWS_DNS_SLOWRATE" ]; then
_info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
_sleep "$AWS_DNS_SLOWRATE"
else
_sleep 1
fi
return 0
fi
_sleep 1
return 1 return 1
} }
@ -59,22 +99,51 @@ dns_aws_rm() {
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
_use_container_role || _use_instance_role
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" _err "invalid domain"
_sleep 1
return 1 return 1
fi fi
_debug _domain_id "$_domain_id" _debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords><ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
_info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
_sleep 1
return 1
fi
if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
_info "txt record deleted success."
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 exist, skip"
_sleep 1
return 0 return 0
fi 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 successfully."
if [ -n "$AWS_DNS_SLOWRATE" ]; then
_info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
_sleep "$AWS_DNS_SLOWRATE"
else
_sleep 1
fi
return 0
fi
_sleep 1
return 1 return 1
} }
@ -87,7 +156,6 @@ _get_root() {
p=1 p=1
if aws_rest GET "2013-04-01/hostedzone"; then if aws_rest GET "2013-04-01/hostedzone"; then
_debug "response" "$response"
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100) h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug2 "Checking domain: $h" _debug2 "Checking domain: $h"
@ -120,7 +188,7 @@ _get_root() {
_domain=$h _domain=$h
return 0 return 0
fi fi
_err "Can not find domain id: $h"
_err "Can't find domain with id: $h"
return 1 return 1
fi fi
fi fi
@ -131,6 +199,55 @@ _get_root() {
return 1 return 1
} }
_use_container_role() {
# automatically set if running inside ECS
if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then
_debug "No ECS environment variable detected"
return 1
fi
_use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
}
_use_instance_role() {
_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
_debug "_url" "$_url"
if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then
_debug "Unable to fetch IAM role from instance metadata"
return 1
fi
_aws_role=$(_get "$_url" "" 1)
_debug "_aws_role" "$_aws_role"
_use_metadata "$_url$_aws_role"
}
_use_metadata() {
_aws_creds="$(
_get "$1" "" 1 \
| _normalizeJson \
| tr '{,}' '\n' \
| while read -r _line; do
_key="$(echo "${_line%%:*}" | tr -d '"')"
_value="${_line#*:}"
_debug3 "_key" "$_key"
_secure_debug3 "_value" "$_value"
case "$_key" in
AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;;
SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;;
Token) echo "AWS_SESSION_TOKEN=$_value" ;;
esac
done \
| paste -sd' ' -
)"
_secure_debug "_aws_creds" "$_aws_creds"
if [ -z "$_aws_creds" ]; then
return 1
fi
eval "$_aws_creds"
_using_role=true
}
#method uri qstr data #method uri qstr data
aws_rest() { aws_rest() {
mtd="$1" mtd="$1"
@ -236,6 +353,7 @@ aws_rest() {
fi fi
_ret="$?" _ret="$?"
_debug2 response "$response"
if [ "$_ret" = "0" ]; then if [ "$_ret" = "0" ]; then
if _contains "$response" "<ErrorResponse"; then if _contains "$response" "<ErrorResponse"; then
_err "Response error:$response" _err "Response error:$response"

348
dnsapi/dns_azure.sh

@ -0,0 +1,348 @@
#!/usr/bin/env sh
WIKI="https://github.com/acmesh-official/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"
return 0
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"
return 0
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
_ret="$?"
_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 [ "$_ret" != "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")"
_ret="$?"
_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 [ "$_ret" != "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=1
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?\$top=500&api-version=2017-09-01" "" "$accesstoken"
# Find matching domain name in 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
if [ "$i" = 1 ]; then
#create the record at the domain apex (@) if only the domain name was provided as --domain-alias
_sub_domain="@"
else
_sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
fi
_domain=$h
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}

114
dnsapi/dns_cf.sh

@ -5,6 +5,10 @@
# #
#CF_Email="xxxx@sss.com" #CF_Email="xxxx@sss.com"
#CF_Token="xxxx"
#CF_Account_ID="xxxx"
#CF_Zone_ID="xxxx"
CF_Api="https://api.cloudflare.com/client/v4" CF_Api="https://api.cloudflare.com/client/v4"
######## Public functions ##################### ######## Public functions #####################
@ -14,13 +18,22 @@ dns_cf_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}"
CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}"
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
if [ "$CF_Token" ]; then
_saveaccountconf_mutable CF_Token "$CF_Token"
_saveaccountconf_mutable CF_Account_ID "$CF_Account_ID"
_saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID"
else
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
CF_Key="" CF_Key=""
CF_Email="" CF_Email=""
_err "You don't specify cloudflare api key and email yet."
_err "Please create you key and try again."
_err "You didn't specify a Cloudflare api key and email yet."
_err "You can get yours from here https://dash.cloudflare.com/profile."
return 1 return 1
fi fi
@ -29,10 +42,10 @@ dns_cf_add() {
_err "Please check and retry." _err "Please check and retry."
return 1 return 1
fi fi
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf_mutable CF_Key "$CF_Key" _saveaccountconf_mutable CF_Key "$CF_Key"
_saveaccountconf_mutable CF_Email "$CF_Email" _saveaccountconf_mutable CF_Email "$CF_Email"
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -46,38 +59,31 @@ dns_cf_add() {
_debug "Getting txt records" _debug "Getting txt records"
_cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain" _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain"
if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then
if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_err "Error" _err "Error"
return 1 return 1
fi fi
count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
_debug count "$count"
if [ "$count" = "0" ]; then
# 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" _info "Adding record"
if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK" _info "Added, OK"
return 0 return 0
elif _contains "$response" "The record already exists"; then
_info "Already exists, OK"
return 0
else else
_err "Add txt record error." _err "Add txt record error."
return 1 return 1
fi fi
fi fi
_err "Add txt record error." _err "Add txt record error."
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 return 1
fi
} }
@ -86,15 +92,11 @@ dns_cf_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}"
CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}"
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" 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" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -108,17 +110,17 @@ dns_cf_rm() {
_debug "Getting txt records" _debug "Getting txt records"
_cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" _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"
if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_err "Error: $response"
return 1 return 1
fi fi
count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ")
_debug count "$count" _debug count "$count"
if [ "$count" = "0" ]; then if [ "$count" = "0" ]; then
_info "Don't need to remove." _info "Don't need to remove."
else else
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
record_id=$(echo "$response" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug "record_id" "$record_id" _debug "record_id" "$record_id"
if [ -z "$record_id" ]; then if [ -z "$record_id" ]; then
_err "Can not get record id to remove." _err "Can not get record id to remove."
@ -128,7 +130,7 @@ dns_cf_rm() {
_err "Delete record error." _err "Delete record error."
return 1 return 1
fi fi
_contains "$response" '"success":true'
echo "$response" | tr -d " " | grep \"success\":true >/dev/null
fi fi
} }
@ -141,8 +143,30 @@ dns_cf_rm() {
# _domain_id=sdjkglgdfewsdfg # _domain_id=sdjkglgdfewsdfg
_get_root() { _get_root() {
domain=$1 domain=$1
i=2
i=1
p=1 p=1
# Use Zone ID directly if provided
if [ "$CF_Zone_ID" ]; then
if ! _cf_rest GET "zones/$CF_Zone_ID"; then
return 1
else
if echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_domain=$(echo "$response" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
if [ "$_domain" ]; then
_cutlength=$((${#domain} - ${#_domain} - 1))
_sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
_domain_id=$CF_Zone_ID
return 0
else
return 1
fi
else
return 1
fi
fi
fi
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100) h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h" _debug h "$h"
@ -151,12 +175,18 @@ _get_root() {
return 1 return 1
fi fi
if [ "$CF_Account_ID" ]; then
if ! _cf_rest GET "zones?name=$h&account.id=$CF_Account_ID"; then
return 1
fi
else
if ! _cf_rest GET "zones?name=$h"; then if ! _cf_rest GET "zones?name=$h"; then
return 1 return 1
fi fi
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 _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_count":1'; then
_domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h _domain=$h
@ -176,9 +206,17 @@ _cf_rest() {
data="$3" data="$3"
_debug "$ep" _debug "$ep"
export _H1="X-Auth-Email: $CF_Email"
export _H2="X-Auth-Key: $CF_Key"
export _H3="Content-Type: application/json"
email_trimmed=$(echo "$CF_Email" | tr -d '"')
key_trimmed=$(echo "$CF_Key" | tr -d '"')
token_trimmed=$(echo "$CF_Token" | tr -d '"')
export _H1="Content-Type: application/json"
if [ "$token_trimmed" ]; then
export _H2="Authorization: Bearer $token_trimmed"
else
export _H2="X-Auth-Email: $email_trimmed"
export _H3="X-Auth-Key: $key_trimmed"
fi
if [ "$m" != "GET" ]; then if [ "$m" != "GET" ]; then
_debug data "$data" _debug data "$data"

197
dnsapi/dns_clouddns.sh

@ -0,0 +1,197 @@
#!/usr/bin/env sh
# Author: Radek Sprta <sprta@vshosting.cz>
#CLOUDDNS_EMAIL=XXXXX
#CLOUDDNS_PASSWORD="YYYYYYYYY"
#CLOUDDNS_CLIENT_ID=XXXXX
CLOUDDNS_API='https://admin.vshosting.cloud/clouddns'
CLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login'
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_clouddns_add() {
fulldomain=$1
txtvalue=$2
_debug "fulldomain" "$fulldomain"
CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}"
CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}"
CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}"
if [ -z "$CLOUDDNS_PASSWORD" ] || [ -z "$CLOUDDNS_EMAIL" ] || [ -z "$CLOUDDNS_CLIENT_ID" ]; then
CLOUDDNS_CLIENT_ID=""
CLOUDDNS_EMAIL=""
CLOUDDNS_PASSWORD=""
_err "You didn't specify a CloudDNS password, email and client ID yet."
return 1
fi
if ! _contains "$CLOUDDNS_EMAIL" "@"; then
_err "It seems that the CLOUDDNS_EMAIL=$CLOUDDNS_EMAIL is not a valid email address."
_err "Please check and retry."
return 1
fi
# Save CloudDNS client id, email and password to config file
_saveaccountconf_mutable CLOUDDNS_CLIENT_ID "$CLOUDDNS_CLIENT_ID"
_saveaccountconf_mutable CLOUDDNS_EMAIL "$CLOUDDNS_EMAIL"
_saveaccountconf_mutable CLOUDDNS_PASSWORD "$CLOUDDNS_PASSWORD"
_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"
# Add TXT record
data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"domainId\":\"$_domain_id\"}"
if _clouddns_api POST "record-txt" "$data"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
elif _contains "$response" '"code":4136'; then
_info "Already exists, OK"
else
_err "Add TXT record error."
return 1
fi
fi
_debug "Publishing record changes"
_clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}"
}
# Usage: rm _acme-challenge.www.domain.com
dns_clouddns_rm() {
fulldomain=$1
_debug "fulldomain" "$fulldomain"
CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}"
CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}"
CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}"
_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"
# Get record ID
_clouddns_api GET "domain/$_domain_id"
if _contains "$response" "lastDomainRecordList"; then
re="\"lastDomainRecordList\".*\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\","
_last_domains=$(echo "$response" | _egrep_o "$re")
re2="\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\","
_record_id=$(echo "$_last_domains" | _egrep_o "$re2" | _head_n 1 | cut -d : -f 2 | cut -d , -f 1 | tr -d "\"")
_debug _record_id "$_record_id"
else
_err "Could not retrieve record ID"
return 1
fi
_info "Removing record"
if _clouddns_api DELETE "record/$_record_id"; then
if _contains "$response" "\"error\":"; then
_err "Could not remove record"
return 1
fi
fi
_debug "Publishing record changes"
_clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}"
}
#################### Private functions below ##################################
# Usage: _get_root _acme-challenge.www.domain.com
# Returns:
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
# Get domain root
data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}]}"
_clouddns_api "POST" "domain/search" "$data"
domain_slice="$domain"
while [ -z "$domain_root" ]; do
if _contains "$response" "\"domainName\":\"$domain_slice\.\""; then
domain_root="$domain_slice"
_debug domain_root "$domain_root"
fi
domain_slice="$(echo "$domain_slice" | cut -d . -f 2-)"
done
# Get domain id
data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}, \
{\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}"
_clouddns_api "POST" "domain/search" "$data"
if _contains "$response" "\"id\":\""; then
re='domainType\":\"[^\"]*\",\"id\":\"([^\"]*)\",' # Match domain id
_domain_id=$(echo "$response" | _egrep_o "$re" | _head_n 1 | cut -d : -f 3 | tr -d "\",")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | sed "s/.$domain_root//")
_domain="$domain_root"
return 0
fi
_err 'Domain name not found on your CloudDNS account'
return 1
fi
return 1
}
# Usage: _clouddns_api GET domain/search '{"data": "value"}'
# Returns:
# response='{"message": "api response"}'
_clouddns_api() {
method=$1
endpoint="$2"
data="$3"
_debug endpoint "$endpoint"
if [ -z "$CLOUDDNS_TOKEN" ]; then
_clouddns_login
fi
_debug CLOUDDNS_TOKEN "$CLOUDDNS_TOKEN"
export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer $CLOUDDNS_TOKEN"
if [ "$method" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$CLOUDDNS_API/$endpoint" "" "$method" | tr -d '\t\r\n ')"
else
response="$(_get "$CLOUDDNS_API/$endpoint" | tr -d '\t\r\n ')"
fi
# shellcheck disable=SC2181
if [ "$?" != "0" ]; then
_err "Error $endpoint"
return 1
fi
_debug2 response "$response"
return 0
}
# Returns:
# CLOUDDNS_TOKEN=dslfje2rj23l
_clouddns_login() {
login_data="{\"email\": \"$CLOUDDNS_EMAIL\", \"password\": \"$CLOUDDNS_PASSWORD\"}"
response="$(_post "$login_data" "$CLOUDDNS_LOGIN_API" "" "POST" "Content-Type: application/json")"
if _contains "$response" "\"accessToken\":\""; then
CLOUDDNS_TOKEN=$(echo "$response" | _egrep_o "\"accessToken\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
export CLOUDDNS_TOKEN
else
echo 'Could not get CloudDNS access token; check your credentials'
return 1
fi
return 0
}

64
dnsapi/dns_cloudns.sh

@ -4,6 +4,7 @@
# Repository: https://github.com/ClouDNS/acme.sh/ # Repository: https://github.com/ClouDNS/acme.sh/
#CLOUDNS_AUTH_ID=XXXXX #CLOUDNS_AUTH_ID=XXXXX
#CLOUDNS_SUB_AUTH_ID=XXXXX
#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" #CLOUDNS_AUTH_PASSWORD="YYYYYYYYY"
CLOUDNS_API="https://api.cloudns.net" CLOUDNS_API="https://api.cloudns.net"
@ -25,14 +26,11 @@ dns_cloudns_add() {
host="$(echo "$1" | sed "s/\.$zone\$//")" host="$(echo "$1" | sed "s/\.$zone\$//")"
record=$2 record=$2
record_id=$(_dns_cloudns_get_record_id "$zone" "$host")
_debug zone "$zone" _debug zone "$zone"
_debug host "$host" _debug host "$host"
_debug record "$record" _debug record "$record"
_debug record_id "$record_id"
if [ -z "$record_id" ]; then
_info "Adding the TXT record for $1" _info "Adding the TXT record for $1"
_dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60" _dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60"
if ! _contains "$response" "\"status\":\"Success\""; then if ! _contains "$response" "\"status\":\"Success\""; then
@ -40,15 +38,6 @@ dns_cloudns_add() {
return 1 return 1
fi fi
_info "Added." _info "Added."
else
_info "Updating the TXT record for $1"
_dns_cloudns_http_api_call "dns/mod-record.json" "domain-name=$zone&record-id=$record_id&record-type=TXT&host=$host&record=$record&ttl=60"
if ! _contains "$response" "\"status\":\"Success\""; then
_err "The TXT record for $1 cannot be updated."
return 1
fi
_info "Updated."
fi
return 0 return 0
} }
@ -71,22 +60,32 @@ dns_cloudns_rm() {
host="$(echo "$1" | sed "s/\.$zone\$//")" host="$(echo "$1" | sed "s/\.$zone\$//")"
record=$2 record=$2
record_id=$(_dns_cloudns_get_record_id "$zone" "$host")
_dns_cloudns_http_api_call "dns/records.json" "domain-name=$zone&host=$host&type=TXT"
if ! _contains "$response" "\"id\":"; then
return 1
fi
for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do
record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g')
if [ ! -z "$record_id" ]; then
_debug zone "$zone" _debug zone "$zone"
_debug host "$host" _debug host "$host"
_debug record "$record" _debug record "$record"
_debug record_id "$record_id" _debug record_id "$record_id"
if [ ! -z "$record_id" ]; then
_info "Deleting the TXT record for $1" _info "Deleting the TXT record for $1"
_dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id"
if ! _contains "$response" "\"status\":\"Success\""; then if ! _contains "$response" "\"status\":\"Success\""; then
_err "The TXT record for $1 cannot be deleted." _err "The TXT record for $1 cannot be deleted."
return 1
fi
else
_info "Deleted." _info "Deleted."
fi fi
fi
done
return 0 return 0
} }
@ -97,17 +96,19 @@ _dns_cloudns_init_check() {
fi fi
CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}" CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}"
CLOUDNS_SUB_AUTH_ID="${CLOUDNS_SUB_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_SUB_AUTH_ID)}"
CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}" CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}"
if [ -z "$CLOUDNS_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then
if [ -z "$CLOUDNS_AUTH_ID$CLOUDNS_SUB_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then
CLOUDNS_AUTH_ID="" CLOUDNS_AUTH_ID=""
CLOUDNS_SUB_AUTH_ID=""
CLOUDNS_AUTH_PASSWORD="" CLOUDNS_AUTH_PASSWORD=""
_err "You don't specify cloudns api id and password yet." _err "You don't specify cloudns api id and password yet."
_err "Please create you id and password and try again." _err "Please create you id and password and try again."
return 1 return 1
fi fi
if [ -z "$CLOUDNS_AUTH_ID" ]; then
_err "CLOUDNS_AUTH_ID is not configured"
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 return 1
fi fi
@ -123,8 +124,9 @@ _dns_cloudns_init_check() {
return 1 return 1
fi fi
#save the api id and password to the account conf file.
# save the api id and password to the account conf file.
_saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" _saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID"
_saveaccountconf_mutable CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID"
_saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD"
CLOUDNS_INIT_CHECK_COMPLETED=1 CLOUDNS_INIT_CHECK_COMPLETED=1
@ -155,30 +157,28 @@ _dns_cloudns_get_zone_name() {
return 1 return 1
} }
_dns_cloudns_get_record_id() {
_dns_cloudns_http_api_call "dns/records.json" "domain-name=$1&host=$2&type=TXT"
if _contains "$response" "\"id\":"; then
echo "$response" | cut -d '"' -f 2
return 0
fi
return 1
}
_dns_cloudns_http_api_call() { _dns_cloudns_http_api_call() {
method=$1 method=$1
_debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID"
_debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID"
_debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD"
if [ ! -z "$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 if [ -z "$2" ]; then
data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD"
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD"
else else
data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD&$2"
data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2"
fi fi
response="$(_get "$CLOUDNS_API/$method?$data")" response="$(_get "$CLOUDNS_API/$method?$data")"
_debug2 response "$response"
_debug response "$response"
return 0 return 0
} }

157
dnsapi/dns_cn.sh

@ -0,0 +1,157 @@
#!/usr/bin/env sh
# DNS API for acme.sh for Core-Networks (https://beta.api.core-networks.de/doc/).
# created by 5ll and francis
CN_API="https://beta.api.core-networks.de"
######## Public functions #####################
dns_cn_add() {
fulldomain=$1
txtvalue=$2
if ! _cn_login; then
_err "login failed"
return 1
fi
_debug "First detect the root zone"
if ! _cn_get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug "_sub_domain $_sub_domain"
_debug "_domain $_domain"
_info "Adding record"
curData="{\"name\":\"$_sub_domain\",\"ttl\":120,\"type\":\"TXT\",\"data\":\"$txtvalue\"}"
curResult="$(_post "${curData}" "${CN_API}/dnszones/${_domain}/records/")"
_debug "curData $curData"
_debug "curResult $curResult"
if _contains "$curResult" ""; then
_info "Added, OK"
if ! _cn_commit; then
_err "commiting changes failed"
return 1
fi
return 0
else
_err "Add txt record error."
_debug "curData is $curData"
_debug "curResult is $curResult"
_err "error adding text record, response was $curResult"
return 1
fi
}
dns_cn_rm() {
fulldomain=$1
txtvalue=$2
if ! _cn_login; then
_err "login failed"
return 1
fi
_debug "First detect the root zone"
if ! _cn_get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_info "Deleting record"
curData="{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\"}"
curResult="$(_post "${curData}" "${CN_API}/dnszones/${_domain}/records/delete")"
_debug curData is "$curData"
_info "commiting changes"
if ! _cn_commit; then
_err "commiting changes failed"
return 1
fi
_info "Deletet txt record"
return 0
}
################### Private functions below ##################################
_cn_login() {
CN_User="${CN_User:-$(_readaccountconf_mutable CN_User)}"
CN_Password="${CN_Password:-$(_readaccountconf_mutable CN_Password)}"
if [ -z "$CN_User" ] || [ -z "$CN_Password" ]; then
CN_User=""
CN_Password=""
_err "You must export variables: CN_User and CN_Password"
return 1
fi
#save the config variables to the account conf file.
_saveaccountconf_mutable CN_User "$CN_User"
_saveaccountconf_mutable CN_Password "$CN_Password"
_info "Getting an AUTH-Token"
curData="{\"login\":\"${CN_User}\",\"password\":\"${CN_Password}\"}"
curResult="$(_post "${curData}" "${CN_API}/auth/token")"
_debug "Calling _CN_login: '${curData}' '${CN_API}/auth/token'"
if _contains "${curResult}" '"token":"'; then
authToken=$(echo "${curResult}" | cut -d ":" -f2 | cut -d "," -f1 | sed 's/^.\(.*\).$/\1/')
export _H1="Authorization: Bearer $authToken"
_info "Successfully acquired AUTH-Token"
_debug "AUTH-Token: '${authToken}'"
_debug "_H1 '${_H1}'"
else
_err "Couldn't acquire an AUTH-Token"
return 1
fi
}
# Commit changes
_cn_commit() {
_info "Commiting changes"
_post "" "${CN_API}/dnszones/$h/records/commit"
}
_cn_get_root() {
domain=$1
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
_debug _H1 "${_H1}"
if [ -z "$h" ]; then
#not valid
return 1
fi
_cn_zonelist="$(_get ${CN_API}/dnszones/)"
_debug _cn_zonelist "${_cn_zonelist}"
if [ "$?" != "0" ]; then
_err "something went wrong while getting the zone list"
return 1
fi
if _contains "$_cn_zonelist" "\"name\":\"$h\"" >/dev/null; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
else
_debug "Zonelist does not contain domain - iterating "
fi
p=$i
i=$(_math "$i" + 1)
done
_err "Zonelist does not contain domain - exiting"
return 1
}

253
dnsapi/dns_conoha.sh

@ -0,0 +1,253 @@
#!/usr/bin/env sh
CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\."
######## Public functions #####################
#Usage: dns_conoha_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_conoha_add() {
fulldomain=$1
txtvalue=$2
_info "Using conoha"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug "Check uesrname and password"
CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
CONOHA_Username=""
CONOHA_Password=""
CONOHA_TenantId=""
CONOHA_IdentityServiceApi=""
_err "You didn't specify a conoha api username and password yet."
_err "Please create the user and try again."
return 1
fi
_saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
_saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
_saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
_saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
accesstoken="$(printf "%s" "$token" | sed -n 1p)"
CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
else
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Adding record"
body="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"data\":\"$txtvalue\",\"ttl\":60}"
if _conoha_rest POST "$CONOHA_Api/v1/domains/$_domain_id/records" "$body" "$accesstoken"; then
if _contains "$response" '"data":"'"$txtvalue"'"'; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_conoha_rm() {
fulldomain=$1
txtvalue=$2
_info "Using conoha"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug "Check uesrname and password"
CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
CONOHA_Username=""
CONOHA_Password=""
CONOHA_TenantId=""
CONOHA_IdentityServiceApi=""
_err "You didn't specify a conoha api username and password yet."
_err "Please create the user and try again."
return 1
fi
_saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
_saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
_saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
_saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
accesstoken="$(printf "%s" "$token" | sed -n 1p)"
CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
else
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
if ! _conoha_rest GET "$CONOHA_Api/v1/domains/$_domain_id/records" "" "$accesstoken"; then
_err "Error"
return 1
fi
record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' \
| grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" \
| _head_n 1 | cut -d : -f 2 | tr -d \")
if [ -z "$record_id" ]; then
_err "Can not get record id to remove."
return 1
fi
_debug record_id "$record_id"
_info "Removing the txt record"
if ! _conoha_rest DELETE "$CONOHA_Api/v1/domains/$_domain_id/records/$record_id" "" "$accesstoken"; then
_err "Delete record error."
return 1
fi
return 0
}
#################### Private functions below ##################################
_conoha_rest() {
m="$1"
ep="$2"
data="$3"
accesstoken="$4"
export _H1="Accept: application/json"
export _H2="Content-Type: application/json"
if [ -n "$accesstoken" ]; then
export _H3="X-Auth-Token: $accesstoken"
fi
_debug "$ep"
if [ "$m" != "GET" ]; then
_secure_debug2 data "$data"
response="$(_post "$data" "$ep" "" "$m")"
else
response="$(_get "$ep")"
fi
_ret="$?"
_secure_debug2 response "$response"
if [ "$_ret" != "0" ]; then
_err "error $ep"
return 1
fi
response="$(printf "%s" "$response" | _normalizeJson)"
return 0
}
_conoha_get_accesstoken() {
ep="$1"
username="$2"
password="$3"
tenantId="$4"
accesstoken="$(_readaccountconf_mutable conoha_accesstoken)"
expires="$(_readaccountconf_mutable conoha_tokenvalidto)"
CONOHA_Api="$(_readaccountconf_mutable conoha_dns_ep)"
# can we reuse the access token?
if [ -n "$accesstoken" ] && [ -n "$expires" ] && [ -n "$CONOHA_Api" ]; then
utc_date="$(_utc_date | sed "s/ /T/")"
if expr "$utc_date" "<" "$expires" >/dev/null; then
# access token is still valid - reuse it
_debug "reusing access token"
printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
return 0
else
_debug "access token expired"
fi
fi
_debug "getting new access token"
body="$(printf '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' "$username" "$password" "$tenantId")"
if ! _conoha_rest POST "$ep" "$body" ""; then
_err error "$response"
return 1
fi
accesstoken=$(printf "%s" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
expires=$(printf "%s" "$response" | _egrep_o "\"expires\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2-4 | tr -d \" | tr -d Z) #expect UTC
if [ -z "$accesstoken" ] || [ -z "$expires" ]; then
_err "no acccess token received. Check your Conoha settings see $WIKI"
return 1
fi
_saveaccountconf_mutable conoha_accesstoken "$accesstoken"
_saveaccountconf_mutable conoha_tokenvalidto "$expires"
CONOHA_Api=$(printf "%s" "$response" | _egrep_o 'publicURL":"'"$CONOHA_DNS_EP_PREFIX_REGEXP"'[^"]*"' | _head_n 1 | cut -d : -f 2-3 | tr -d \")
if [ -z "$CONOHA_Api" ]; then
_err "failed to get conoha dns endpoint url"
return 1
fi
_saveaccountconf_mutable conoha_dns_ep "$CONOHA_Api"
printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
return 0
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain="$1"
ep="$2"
accesstoken="$3"
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100).
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _conoha_rest GET "$ep/v1/domains?name=$h" "" "$accesstoken"; then
return 1
fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}

141
dnsapi/dns_constellix.sh

@ -0,0 +1,141 @@
#!/usr/bin/env sh
# Author: Wout Decre <wout@canodus.be>
CONSTELLIX_Api="https://api.dns.constellix.com/v1"
#CONSTELLIX_Key="XXX"
#CONSTELLIX_Secret="XXX"
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_constellix_add() {
fulldomain=$1
txtvalue=$2
CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}"
CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}"
if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then
_err "You did not specify the Contellix API key and secret yet."
return 1
fi
_saveaccountconf_mutable CONSTELLIX_Key "$CONSTELLIX_Key"
_saveaccountconf_mutable CONSTELLIX_Secret "$CONSTELLIX_Secret"
if ! _get_root "$fulldomain"; then
_err "Invalid domain"
return 1
fi
_info "Adding TXT record"
if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"add\":true,\"set\":{\"name\":\"${_sub_domain}\",\"ttl\":120,\"roundRobin\":[{\"value\":\"${txtvalue}\"}]}}]"; then
if printf -- "%s" "$response" | grep "{\"success\":\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\"}" >/dev/null; then
_info "Added"
return 0
else
_err "Error adding TXT record"
return 1
fi
fi
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_constellix_rm() {
fulldomain=$1
txtvalue=$2
CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}"
CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}"
if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then
_err "You did not specify the Contellix API key and secret yet."
return 1
fi
if ! _get_root "$fulldomain"; then
_err "Invalid domain"
return 1
fi
_info "Removing TXT record"
if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"delete\":true,\"filter\":{\"field\":\"name\",\"op\":\"eq\",\"value\":\"${_sub_domain}\"}}]"; then
if printf -- "%s" "$response" | grep "{\"success\":\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\"}" >/dev/null; then
_info "Removed"
return 0
else
_err "Error removing TXT record"
return 1
fi
fi
}
#################### Private functions below ##################################
_get_root() {
domain=$1
i=2
p=1
_debug "Detecting root zone"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
return 1
fi
if ! _constellix_rest GET "domains/search?exact=$h"; then
return 1
fi
if _contains "$response" "\"name\":\"$h\""; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]+" | cut -d ':' -f 2)
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p)
_domain="$h"
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_constellix_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
rdate=$(date +"%s")"000"
hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64)
export _H1="x-cnsdns-apiKey: $CONSTELLIX_Key"
export _H2="x-cnsdns-requestDate: $rdate"
export _H3="x-cnsdns-hmac: $hmac"
export _H4="Accept: application/json"
export _H5="Content-Type: application/json"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$CONSTELLIX_Api/$ep" "" "$m")"
else
response="$(_get "$CONSTELLIX_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "Error $ep"
return 1
fi
_debug response "$response"
return 0
}

54
dnsapi/dns_cx.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# Cloudxns.com Domain api
# CloudXNS Domain api
# #
#CX_Key="1234" #CX_Key="1234"
# #
@ -16,10 +16,12 @@ dns_cx_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
CX_Key="${CX_Key:-$(_readaccountconf_mutable CX_Key)}"
CX_Secret="${CX_Secret:-$(_readaccountconf_mutable CX_Secret)}"
if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then
CX_Key="" CX_Key=""
CX_Secret="" CX_Secret=""
_err "You don't specify cloudxns.com api key or secret yet."
_err "You don't specify cloudxns.net api key or secret yet."
_err "Please create you key and try again." _err "Please create you key and try again."
return 1 return 1
fi fi
@ -27,8 +29,8 @@ dns_cx_add() {
REST_API="$CX_Api" REST_API="$CX_Api"
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf CX_Key "$CX_Key"
_saveaccountconf CX_Secret "$CX_Secret"
_saveaccountconf_mutable CX_Key "$CX_Key"
_saveaccountconf_mutable CX_Secret "$CX_Secret"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -36,33 +38,20 @@ dns_cx_add() {
return 1 return 1
fi fi
existing_records "$_domain" "$_sub_domain"
_debug count "$count"
if [ "$?" != "0" ]; then
_err "Error get existing records."
return 1
fi
if [ "$count" = "0" ]; then
add_record "$_domain" "$_sub_domain" "$txtvalue" add_record "$_domain" "$_sub_domain" "$txtvalue"
else
update_record "$_domain" "$_sub_domain" "$txtvalue"
fi
if [ "$?" = "0" ]; then
return 0
fi
return 1
} }
#fulldomain
#fulldomain txtvalue
dns_cx_rm() { dns_cx_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2
CX_Key="${CX_Key:-$(_readaccountconf_mutable CX_Key)}"
CX_Secret="${CX_Secret:-$(_readaccountconf_mutable CX_Secret)}"
REST_API="$CX_Api" REST_API="$CX_Api"
if _get_root "$fulldomain"; then if _get_root "$fulldomain"; then
record_id="" record_id=""
existing_records "$_domain" "$_sub_domain"
if ! [ "$record_id" = "" ]; then
existing_records "$_domain" "$_sub_domain" "$txtvalue"
if [ "$record_id" ]; then
_rest DELETE "record/$record_id/$_domain_id" "{}" _rest DELETE "record/$record_id/$_domain_id" "{}"
_info "Deleted record ${fulldomain}" _info "Deleted record ${fulldomain}"
fi fi
@ -77,7 +66,6 @@ existing_records() {
_debug "Getting txt records" _debug "Getting txt records"
root=$1 root=$1
sub=$2 sub=$2
count=0
if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then
return 1 return 1
fi fi
@ -89,7 +77,6 @@ existing_records() {
fi fi
if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then
count=1
record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1)
_debug record_id "$record_id" _debug record_id "$record_id"
return 0 return 0
@ -114,23 +101,6 @@ add_record() {
return 0 return 0
} }
#update the txt record
#Usage: root sub txtvalue
update_record() {
root=$1
sub=$2
txtvalue=$3
fulldomain="$sub.$root"
_info "Updating record"
if _rest PUT "record/$record_id" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then
return 0
fi
return 1
}
#################### Private functions below ################################## #################### Private functions below ##################################
#_acme-challenge.www.domain.com #_acme-challenge.www.domain.com
#returns #returns

2
dnsapi/dns_cyon.sh

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/usr/bin/env sh
######## ########
# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh)
# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/acmesh-official/acme.sh)
# #
# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com # Usage: acme.sh --issue --dns dns_cyon -d www.domain.com
# #

184
dnsapi/dns_da.sh

@ -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}&amp;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
}

130
dnsapi/dns_ddnss.sh

@ -0,0 +1,130 @@
#!/usr/bin/env sh
#Created by RaidenII, to use DuckDNS's API to add/remove text records
#modified by helbgd @ 03/13/2018 to support ddnss.de
#modified by mod242 @ 04/24/2018 to support different ddnss domains
#Please note: the Wildcard Feature must be turned on for the Host record
#and the checkbox for TXT needs to be enabled
# Pass credentials before "acme.sh --issue --dns dns_ddnss ..."
# --
# export DDNSS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
# --
#
DDNSS_DNS_API="https://ip4.ddnss.de/upd.php"
######## Public functions #####################
#Usage: dns_ddnss_add _acme-challenge.domain.ddnss.de "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ddnss_add() {
fulldomain=$1
txtvalue=$2
DDNSS_Token="${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}"
if [ -z "$DDNSS_Token" ]; then
_err "You must export variable: DDNSS_Token"
_err "The token for your DDNSS account is necessary."
_err "You can look it up in your DDNSS account."
return 1
fi
# Now save the credentials.
_saveaccountconf_mutable DDNSS_Token "$DDNSS_Token"
# Unfortunately, DDNSS 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 DDNSS
if ! _ddnss_get_domain; then
return 1
fi
# Now add the TXT record to DDNSS DNS
_info "Trying to add TXT record"
if _ddnss_rest GET "key=$DDNSS_Token&host=$_ddnss_domain&txtm=1&txt=$txtvalue"; then
if [ "$response" = "Updated 1 hostname." ]; then
_info "TXT record has been successfully added to your DDNSS 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_ddnss_rm() {
fulldomain=$1
txtvalue=$2
DDNSS_Token="${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}"
if [ -z "$DDNSS_Token" ]; then
_err "You must export variable: DDNSS_Token"
_err "The token for your DDNSS account is necessary."
_err "You can look it up in your DDNSS account."
return 1
fi
if ! _ddnss_get_domain; then
return 1
fi
# Now remove the TXT record from DDNS DNS
_info "Trying to remove TXT record"
if _ddnss_rest GET "key=$DDNSS_Token&host=$_ddnss_domain&txtm=1&txt=."; then
if [ "$response" = "Updated 1 hostname." ]; then
_info "TXT record has been successfully removed from your DDNSS 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.ddnss.de
#returns
# _ddnss_domain=domain
_ddnss_get_domain() {
# We'll extract the domain/username from full domain
_ddnss_domain="$(echo "$fulldomain" | _lower_case | _egrep_o '[.][^.][^.]*[.](ddnss|dyn-ip24|dyndns|dyn|dyndns1|home-webserver|myhome-server|dynip)\..*' | cut -d . -f 2-)"
if [ -z "$_ddnss_domain" ]; then
_err "Error extracting the domain."
return 1
fi
return 0
}
#Usage: method URI
_ddnss_rest() {
method=$1
param="$2"
_debug param "$param"
url="$DDNSS_DNS_API?$param"
_debug url "$url"
# DDNSS uses GET to update domain info
if [ "$method" = "GET" ]; then
response="$(_get "$url" | sed 's/<[a-zA-Z\/][^>]*>//g' | tr -s "\n" | _tail_n 1)"
else
_err "Unsupported method"
return 1
fi
_debug2 response "$response"
return 0
}

204
dnsapi/dns_desec.sh

@ -0,0 +1,204 @@
#!/usr/bin/env sh
#
# deSEC.io Domain API
#
# Author: Zheng Qian
#
# deSEC API doc
# https://desec.readthedocs.io/en/latest/
REST_API="https://desec.io/api/v1/domains"
######## Public functions #####################
#Usage: dns_desec_add _acme-challenge.foobar.dedyn.io "d41d8cd98f00b204e9800998ecf8427e"
dns_desec_add() {
fulldomain=$1
txtvalue=$2
_info "Using desec.io api"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}"
DEDYN_NAME="${DEDYN_NAME:-$(_readaccountconf_mutable DEDYN_NAME)}"
if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
DEDYN_TOKEN=""
DEDYN_NAME=""
_err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create your key and try again."
_err "e.g."
_err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
_err "export DEDYN_NAME=foobar.dedyn.io"
return 1
fi
#save the api token and name to the account conf file.
_saveaccountconf_mutable DEDYN_TOKEN "$DEDYN_TOKEN"
_saveaccountconf_mutable DEDYN_NAME "$DEDYN_NAME"
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$REST_API/"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
# Get existing TXT record
_debug "Getting txt records"
txtvalues="\"\\\"$txtvalue\\\"\""
_desec_rest GET "$REST_API/$DEDYN_NAME/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
for oldtxtvalue in $oldtxtvalues; do
txtvalues="$txtvalues, \"\\\"$oldtxtvalue\\\"\""
done
fi
fi
_debug txtvalues "$txtvalues"
_info "Adding record"
body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":60}]"
if _desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_desec_rm() {
fulldomain=$1
txtvalue=$2
_info "Using desec.io api"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}"
DEDYN_NAME="${DEDYN_NAME:-$(_readaccountconf_mutable DEDYN_NAME)}"
if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
DEDYN_TOKEN=""
DEDYN_NAME=""
_err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create your key and try again."
_err "e.g."
_err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
_err "export DEDYN_NAME=foobar.dedyn.io"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$REST_API/"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
# Get existing TXT record
_debug "Getting txt records"
txtvalues=""
_desec_rest GET "$REST_API/$DEDYN_NAME/rrsets/$_sub_domain/TXT/"
if [ "$_code" = "200" ]; then
oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
_debug "existing TXT found"
_debug oldtxtvalues "$oldtxtvalues"
if [ -n "$oldtxtvalues" ]; then
for oldtxtvalue in $oldtxtvalues; do
if [ "$txtvalue" != "$oldtxtvalue" ]; then
txtvalues="$txtvalues, \"\\\"$oldtxtvalue\\\"\""
fi
done
fi
fi
txtvalues="$(echo "$txtvalues" | cut -c3-)"
_debug txtvalues "$txtvalues"
_info "Deleting record"
body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":60}]"
_desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body"
if [ "$_code" = "200" ]; then
_info "Deleted, OK"
return 0
fi
_err "Delete txt record error."
return 1
}
#################### Private functions below ##################################
_desec_rest() {
m="$1"
ep="$2"
data="$3"
export _H1="Authorization: Token $DEDYN_TOKEN"
export _H2="Accept: application/json"
export _H3="Content-Type: application/json"
if [ "$m" != "GET" ]; then
_secure_debug2 data "$data"
response="$(_post "$data" "$ep" "" "$m")"
else
response="$(_get "$ep")"
fi
_ret="$?"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
_secure_debug2 response "$response"
if [ "$_ret" != "0" ]; then
_err "error $ep"
return 1
fi
response="$(printf "%s" "$response" | _normalizeJson)"
return 0
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain="$1"
ep="$2"
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 ! _desec_rest GET "$ep"; then
return 1
fi
if _contains "$response" "\"name\":\"$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
return 1
}

65
dnsapi/dns_df.sh

@ -0,0 +1,65 @@
#!/usr/bin/env sh
########################################################################
# https://dyndnsfree.de hook script for acme.sh
#
# Environment variables:
#
# - $DF_user (your dyndnsfree.de username)
# - $DF_password (your dyndnsfree.de password)
#
# Author: Thilo Gass <thilo.gass@gmail.com>
# Git repo: https://github.com/ThiloGa/acme.sh
#-- dns_df_add() - Add TXT record --------------------------------------
# Usage: dns_df_add _acme-challenge.subdomain.domain.com "XyZ123..."
dyndnsfree_api="https://dynup.de/acme.php"
dns_df_add() {
fulldomain=$1
txt_value=$2
_info "Using DNS-01 dyndnsfree.de hook"
DF_user="${DF_user:-$(_readaccountconf_mutable DF_user)}"
DF_password="${DF_password:-$(_readaccountconf_mutable DF_password)}"
if [ -z "$DF_user" ] || [ -z "$DF_password" ]; then
DF_user=""
DF_password=""
_err "No auth details provided. Please set user credentials using the \$DF_user and \$DF_password environment variables."
return 1
fi
#save the api user and password to the account conf file.
_debug "Save user and password"
_saveaccountconf_mutable DF_user "$DF_user"
_saveaccountconf_mutable DF_password "$DF_password"
domain="$(printf "%s" "$fulldomain" | cut -d"." -f2-)"
get="$dyndnsfree_api?username=$DF_user&password=$DF_password&hostname=$domain&add_hostname=$fulldomain&txt=$txt_value"
if ! erg="$(_get "$get")"; then
_err "error Adding $fulldomain TXT: $txt_value"
return 1
fi
if _contains "$erg" "success"; then
_info "Success, TXT Added, OK"
else
_err "error Adding $fulldomain TXT: $txt_value erg: $erg"
return 1
fi
_debug "ok Auto $fulldomain TXT: $txt_value erg: $erg"
return 0
}
dns_df_rm() {
fulldomain=$1
txtvalue=$2
_info "TXT enrty in $fulldomain is deleted automatically"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
}

99
dnsapi/dns_dgon.sh

@ -20,12 +20,22 @@
dns_dgon_add() { dns_dgon_add() {
fulldomain="$(echo "$1" | _lower_case)" fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2 txtvalue=$2
DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
# Check if API Key Exist
if [ -z "$DO_API_KEY" ]; then
DO_API_KEY=""
_err "You did not specify DigitalOcean API key."
_err "Please export DO_API_KEY and try again."
return 1
fi
_info "Using digitalocean dns validation - add record" _info "Using digitalocean dns validation - add record"
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue" _debug txtvalue "$txtvalue"
## save the env vars (key and domain split location) for later automated use ## save the env vars (key and domain split location) for later automated use
_saveaccountconf DO_API_KEY "$DO_API_KEY"
_saveaccountconf_mutable DO_API_KEY "$DO_API_KEY"
## split the domain for DO API ## split the domain for DO API
if ! _get_base_domain "$fulldomain"; then if ! _get_base_domain "$fulldomain"; then
@ -39,7 +49,7 @@ dns_dgon_add() {
export _H1="Content-Type: application/json" export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer $DO_API_KEY" export _H2="Authorization: Bearer $DO_API_KEY"
PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records' PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records'
PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}'
PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'","ttl":120}'
_debug PURL "$PURL" _debug PURL "$PURL"
_debug PBODY "$PBODY" _debug PBODY "$PBODY"
@ -65,6 +75,16 @@ dns_dgon_add() {
dns_dgon_rm() { dns_dgon_rm() {
fulldomain="$(echo "$1" | _lower_case)" fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2 txtvalue=$2
DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
# Check if API Key Exist
if [ -z "$DO_API_KEY" ]; then
DO_API_KEY=""
_err "You did not specify DigitalOcean API key."
_err "Please export DO_API_KEY and try again."
return 1
fi
_info "Using digitalocean dns validation - remove record" _info "Using digitalocean dns validation - remove record"
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue" _debug txtvalue "$txtvalue"
@ -84,33 +104,31 @@ dns_dgon_rm() {
## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} ## 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" GURL="https://api.digitalocean.com/v2/domains/$_domain/records"
## while we dont have a record ID we keep going
while [ -z "$record" ]; do
## Get all the matching records
while true; do
## 1) get the URL ## 1) get the URL
## the create request - get ## the create request - get
## args: URL, [onlyheader, timeout] ## args: URL, [onlyheader, timeout]
domain_list="$(_get "$GURL")" 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"
## check response
if [ "$?" != "0" ]; then
_err "error in domain_list response: $domain_list"
return 1 return 1
fi fi
_debug2 nextpage "$nextpage"
GURL="$nextpage"
fi
## we break out of the loop when we have a record
done
_debug2 domain_list "$domain_list"
## 2) find records
## check for what we are looking for: "type":"A","name":"$_sub_domain"
record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
## we found the record
rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")"
_debug rec_id "$rec_id"
if [ ! -z "$record" ]; then
## we found records
rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
_debug rec_ids "$rec_ids"
if [ ! -z "$rec_ids" ]; then
echo "$rec_ids" | while IFS= read -r rec_id; do
## delete the record ## delete the record
## delete URL for removing the one we dont want ## delete URL for removing the one we dont want
DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id"
@ -126,6 +144,20 @@ dns_dgon_rm() {
fi fi
_debug2 response "$response" _debug2 response "$response"
done
fi
fi
## 3) find the next page
nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")"
if [ -z "$nextpage" ]; then
break
fi
_debug2 nextpage "$nextpage"
GURL="$nextpage"
done
## finished correctly ## finished correctly
return 0 return 0
} }
@ -146,7 +178,7 @@ dns_dgon_rm() {
## _domain="domain.com" ## _domain="domain.com"
_get_base_domain() { _get_base_domain() {
# args # args
fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
fulldomain="$(echo "$1" | _lower_case)"
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
# domain max legal length = 253 # domain max legal length = 253
@ -158,10 +190,12 @@ _get_base_domain() {
export _H2="Authorization: Bearer $DO_API_KEY" export _H2="Authorization: Bearer $DO_API_KEY"
_debug DO_API_KEY "$DO_API_KEY" _debug DO_API_KEY "$DO_API_KEY"
## get URL for the list of domains ## get URL for the list of domains
## havent seen this request paginated, tested with 18 domains (more requires manual requests with DO)
## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
DOMURL="https://api.digitalocean.com/v2/domains" DOMURL="https://api.digitalocean.com/v2/domains"
## get the domain list (DO gives basically a full XFER!)
## while we dont have a matching domain we keep going
while [ -z "$found" ]; do
## get the domain list (current page)
domain_list="$(_get "$DOMURL")" domain_list="$(_get "$DOMURL")"
## check response ## check response
@ -179,9 +213,7 @@ _get_base_domain() {
_domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
## check we got something back from our cut (or are we at the end) ## check we got something back from our cut (or are we at the end)
if [ -z "$_domain" ]; then if [ -z "$_domain" ]; then
## we got to the end of the domain - invalid domain
_err "domain not found in DigitalOcean account"
return 1
break
fi fi
## we got part of a domain back - grep it out ## we got part of a domain back - grep it out
found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")"
@ -198,6 +230,19 @@ _get_base_domain() {
i=$(_math $i + 1) i=$(_math $i + 1)
done done
if [ -z "$found" ]; 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\=[0-9]+")"
if [ -z "$nextpage" ]; then
_err "no record and no nextpage in digital ocean DNS removal"
return 1
fi
_debug2 nextpage "$nextpage"
DOMURL="$nextpage"
fi
done
## we went through the entire domain zone list and dint find one that matched ## we went through the entire domain zone list and dint find one that matched
## doesnt look like we can add in the record ## doesnt look like we can add in the record
_err "domain not found in DigitalOcean account, but we should never get here" _err "domain not found in DigitalOcean account, but we should never get here"

35
dnsapi/dns_dnsimple.sh

@ -39,7 +39,6 @@ dns_dnsimple_add() {
_get_records "$_account_id" "$_domain" "$_sub_domain" _get_records "$_account_id" "$_domain" "$_sub_domain"
if [ "$_records_count" = "0" ]; then
_info "Adding record" _info "Adding record"
if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then 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 if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
@ -51,22 +50,6 @@ dns_dnsimple_add() {
fi fi
fi fi
_err "Add txt record error." _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 # fulldomain
@ -84,19 +67,19 @@ dns_dnsimple_rm() {
fi fi
_get_records "$_account_id" "$_domain" "$_sub_domain" _get_records "$_account_id" "$_domain" "$_sub_domain"
_extract_record_id "$_records" "$_sub_domain"
_extract_record_id "$_records" "$_sub_domain"
if [ "$_record_id" ]; then if [ "$_record_id" ]; then
if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then
_info "removed record" "$_record_id"
echo "$_record_id" | while read -r item; do
if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$item"; then
_info "removed record" "$item"
return 0 return 0
else
_err "failed to remove record" "$item"
return 1
fi fi
done
fi fi
_err "failed to remove record" "$_record_id"
return 1
} }
#################### Private functions bellow ################################## #################### Private functions bellow ##################################
@ -169,7 +152,7 @@ _get_records() {
sub_domain=$3 sub_domain=$3
_debug "fetching txt records" _debug "fetching txt records"
_dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100"
_dnsimple_rest GET "$account_id/zones/$domain/records?per_page=5000&sort=id:desc"
if ! _contains "$response" "\"id\":"; then if ! _contains "$response" "\"id\":"; then
_err "failed to retrieve records" _err "failed to retrieve records"

59
dnsapi/dns_doapi.sh

@ -0,0 +1,59 @@
#!/usr/bin/env sh
# Official Let's Encrypt API for do.de / Domain-Offensive
#
# This is different from the dns_do adapter, because dns_do is only usable for enterprise customers
# This API is also available to private customers/individuals
#
# Provide the required LetsEncrypt token like this:
# DO_LETOKEN="FmD408PdqT1E269gUK57"
DO_API="https://www.do.de/api/letsencrypt"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_doapi_add() {
fulldomain=$1
txtvalue=$2
DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}"
if [ -z "$DO_LETOKEN" ]; then
DO_LETOKEN=""
_err "You didn't configure a do.de API token yet."
_err "Please set DO_LETOKEN and try again."
return 1
fi
_saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN"
_info "Adding TXT record to ${fulldomain}"
response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&value=${txtvalue}")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not create resource record, check logs"
_err "${response}"
return 1
}
dns_doapi_rm() {
fulldomain=$1
DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}"
if [ -z "$DO_LETOKEN" ]; then
DO_LETOKEN=""
_err "You didn't configure a do.de API token yet."
_err "Please set DO_LETOKEN and try again."
return 1
fi
_saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN"
_info "Deleting resource record $fulldomain"
response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&action=delete")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not delete resource record, check logs"
_err "${response}"
return 1
}

155
dnsapi/dns_domeneshop.sh

@ -0,0 +1,155 @@
#!/usr/bin/env sh
DOMENESHOP_Api_Endpoint="https://api.domeneshop.no/v0"
##################### Public functions #####################
# Usage: dns_domeneshop_add <full domain> <txt record>
# Example: dns_domeneshop_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_domeneshop_add() {
fulldomain=$1
txtvalue=$2
# Get token and secret
DOMENESHOP_Token="${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}"
DOMENESHOP_Secret="${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}"
if [ -z "$DOMENESHOP_Token" ] || [ -z "$DOMENESHOP_Secret" ]; then
DOMENESHOP_Token=""
DOMENESHOP_Secret=""
_err "You need to spesify a Domeneshop/Domainnameshop API Token and Secret."
return 1
fi
# Save the api token and secret.
_saveaccountconf_mutable DOMENESHOP_Token "$DOMENESHOP_Token"
_saveaccountconf_mutable DOMENESHOP_Secret "$DOMENESHOP_Secret"
# Get the domain name id
if ! _get_domainid "$fulldomain"; then
_err "Did not find domainname"
return 1
fi
# Create record
_domeneshop_rest POST "domains/$_domainid/dns" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":120}"
}
# Usage: dns_domeneshop_rm <full domain> <txt record>
# Example: dns_domeneshop_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_domeneshop_rm() {
fulldomain=$1
txtvalue=$2
# Get token and secret
DOMENESHOP_Token="${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}"
DOMENESHOP_Secret="${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}"
if [ -z "$DOMENESHOP_Token" ] || [ -z "$DOMENESHOP_Secret" ]; then
DOMENESHOP_Token=""
DOMENESHOP_Secret=""
_err "You need to spesify a Domeneshop/Domainnameshop API Token and Secret."
return 1
fi
# Get the domain name id
if ! _get_domainid "$fulldomain"; then
_err "Did not find domainname"
return 1
fi
# Find record
if ! _get_recordid "$_domainid" "$_sub_domain" "$txtvalue"; then
_err "Did not find dns record"
return 1
fi
# Remove record
_domeneshop_rest DELETE "domains/$_domainid/dns/$_recordid"
}
##################### Private functions #####################
_get_domainid() {
domain=$1
# Get domains
_domeneshop_rest GET "domains"
if ! _contains "$response" "\"id\":"; then
_err "failed to get domain names"
return 1
fi
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug "h" "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if _contains "$response" "\"$h\"" >/dev/null; then
# We have found the domain name.
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
_domainid=$(printf "%s" "$response" | _egrep_o "[^{]*\"domain\":\"$_domain\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2)
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_get_recordid() {
domainid=$1
subdomain=$2
txtvalue=$3
# Get all dns records for the domainname
_domeneshop_rest GET "domains/$domainid/dns"
if ! _contains "$response" "\"id\":"; then
_debug "No records in dns"
return 1
fi
if ! _contains "$response" "\"host\":\"$subdomain\""; then
_debug "Record does not exist"
return 1
fi
# Get the id of the record in question
_recordid=$(printf "%s" "$response" | _egrep_o "[^{]*\"host\":\"$subdomain\"[^}]*" | _egrep_o "[^{]*\"data\":\"$txtvalue\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2)
if [ -z "$_recordid" ]; then
return 1
fi
return 0
}
_domeneshop_rest() {
method=$1
endpoint=$2
data=$3
credentials=$(printf "%b" "$DOMENESHOP_Token:$DOMENESHOP_Secret" | _base64)
export _H1="Authorization: Basic $credentials"
export _H2="Content-Type: application/json"
if [ "$method" != "GET" ]; then
response="$(_post "$data" "$DOMENESHOP_Api_Endpoint/$endpoint" "" "$method")"
else
response="$(_get "$DOMENESHOP_Api_Endpoint/$endpoint")"
fi
if [ "$?" != "0" ]; then
_err "error $endpoint"
return 1
fi
return 0
}

96
dnsapi/dns_dp.sh

@ -15,6 +15,8 @@ dns_dp_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}"
DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}"
if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then
DP_Id="" DP_Id=""
DP_Key="" DP_Key=""
@ -24,8 +26,8 @@ dns_dp_add() {
fi fi
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf DP_Id "$DP_Id"
_saveaccountconf DP_Key "$DP_Key"
_saveaccountconf_mutable DP_Id "$DP_Id"
_saveaccountconf_mutable DP_Key "$DP_Key"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -33,31 +35,25 @@ dns_dp_add() {
return 1 return 1
fi fi
existing_records "$_domain" "$_sub_domain"
_debug count "$count"
if [ "$?" != "0" ]; then
_err "Error get existing records."
return 1
fi
if [ "$count" = "0" ]; then
add_record "$_domain" "$_sub_domain" "$txtvalue" add_record "$_domain" "$_sub_domain" "$txtvalue"
else
update_record "$_domain" "$_sub_domain" "$txtvalue"
fi
} }
#fulldomain txtvalue #fulldomain txtvalue
dns_dp_rm() { dns_dp_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}"
DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" _err "invalid domain"
return 1 return 1
fi fi
if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
_err "Record.Lis error." _err "Record.Lis error."
return 1 return 1
fi fi
@ -67,51 +63,20 @@ dns_dp_rm() {
return 0 return 0
fi fi
record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \")
record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"id"' | cut -d : -f 2 | cut -d '"' -f 2)
_debug record_id "$record_id" _debug record_id "$record_id"
if [ -z "$record_id" ]; then if [ -z "$record_id" ]; then
_err "Can not get record id." _err "Can not get record id."
return 1 return 1
fi fi
if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then
if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&record_id=$record_id"; then
_err "Record.Remove error." _err "Record.Remove error."
return 1 return 1
fi fi
_contains "$response" "Action completed successful"
}
#usage: root sub
#return if the sub record already exists.
#echos the existing records count.
# '0' means doesn't exist
existing_records() {
_debug "Getting txt records"
root=$1
sub=$2
if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
return 1
fi
if _contains "$response" 'No records'; then
count=0
return 0
fi
if _contains "$response" "Action completed successful"; then
count=$(printf "%s" "$response" | grep -c '<type>TXT</type>' | tr -d ' ')
record_id=$(printf "%s" "$response" | grep '^<id>' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1)
_debug record_id "$record_id"
return 0
else
_err "get existing records error."
return 1
fi
_contains "$response" "successful"
count=0
} }
#add the txt record. #add the txt record.
@ -124,38 +89,11 @@ add_record() {
_info "Adding record" _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
if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then
return 1 return 1
fi fi
if _contains "$response" "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 _contains "$response" "Action completed successful"; then
return 0
fi
return 1 #error
_contains "$response" "successful" || _contains "$response" "Domain record already exists"
} }
#################### Private functions below ################################## #################### Private functions below ##################################
@ -175,11 +113,11 @@ _get_root() {
return 1 return 1
fi fi
if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then
if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain=$h"; then
return 1 return 1
fi fi
if _contains "$response" "Action completed successful"; then
if _contains "$response" "successful"; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id" _debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then

161
dnsapi/dns_dpi.sh

@ -0,0 +1,161 @@
#!/usr/bin/env sh
# Dnspod.com Domain api
#
#DPI_Id="1234"
#
#DPI_Key="sADDsdasdgdsf"
REST_API="https://api.dnspod.com"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dpi_add() {
fulldomain=$1
txtvalue=$2
DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
if [ -z "$DPI_Id" ] || [ -z "$DPI_Key" ]; then
DPI_Id=""
DPI_Key=""
_err "You don't specify dnspod api key and key id yet."
_err "Please create you key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable DPI_Id "$DPI_Id"
_saveaccountconf_mutable DPI_Key "$DPI_Key"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
add_record "$_domain" "$_sub_domain" "$txtvalue"
}
#fulldomain txtvalue
dns_dpi_rm() {
fulldomain=$1
txtvalue=$2
DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
if ! _rest POST "Record.List" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
_err "Record.Lis error."
return 1
fi
if _contains "$response" 'No records'; then
_info "Don't need to remove."
return 0
fi
record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \")
_debug record_id "$record_id"
if [ -z "$record_id" ]; then
_err "Can not get record id."
return 1
fi
if ! _rest POST "Record.Remove" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then
_err "Record.Remove error."
return 1
fi
_contains "$response" "Action completed successful"
}
#add the txt record.
#usage: root sub txtvalue
add_record() {
root=$1
sub=$2
txtvalue=$3
fulldomain="$sub.$root"
_info "Adding record"
if ! _rest POST "Record.Create" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default"; then
return 1
fi
_contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists"
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _rest POST "Domain.Info" "user_token=$DPI_Id,$DPI_Key&format=json&domain=$h"; then
return 1
fi
if _contains "$response" "Action completed successful"; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"
return 0
fi
return 1
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
#Usage: method URI data
_rest() {
m="$1"
ep="$2"
data="$3"
_debug "$ep"
url="$REST_API/$ep"
_debug url "$url"
if [ "$m" = "GET" ]; then
response="$(_get "$url" | tr -d '\r')"
else
_debug2 data "$data"
response="$(_post "$data" "$url" | tr -d '\r')"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

97
dnsapi/dns_dreamhost.sh

@ -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"
}

176
dnsapi/dns_durabledns.sh

@ -0,0 +1,176 @@
#!/usr/bin/env sh
#DD_API_User="xxxxx"
#DD_API_Key="xxxxxx"
_DD_BASE="https://durabledns.com/services/dns"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_durabledns_add() {
fulldomain=$1
txtvalue=$2
DD_API_User="${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}"
DD_API_Key="${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}"
if [ -z "$DD_API_User" ] || [ -z "$DD_API_Key" ]; then
DD_API_User=""
DD_API_Key=""
_err "You didn't specify a durabledns api user or key yet."
_err "You can get yours from here https://durabledns.com/dashboard/index.php"
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable DD_API_User "$DD_API_User"
_saveaccountconf_mutable DD_API_Key "$DD_API_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"
_dd_soap createRecord string zonename "$_domain." string name "$_sub_domain" string type "TXT" string data "$txtvalue" int aux 0 int ttl 10 string ddns_enabled N
_contains "$response" "createRecordResponse"
}
dns_durabledns_rm() {
fulldomain=$1
txtvalue=$2
DD_API_User="${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}"
DD_API_Key="${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}"
if [ -z "$DD_API_User" ] || [ -z "$DD_API_Key" ]; then
DD_API_User=""
DD_API_Key=""
_err "You didn't specify a durabledns api user or key yet."
_err "You can get yours from here https://durabledns.com/dashboard/index.php"
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 "Find record id"
if ! _dd_soap listRecords string zonename "$_domain."; then
_err "can not listRecords"
return 1
fi
subtxt="$(echo "$txtvalue" | cut -c 1-30)"
record="$(echo "$response" | sed 's/<item\>/#<item>/g' | tr '#' '\n' | grep ">$subtxt")"
_debug record "$record"
if [ -z "$record" ]; then
_err "can not find record for txtvalue" "$txtvalue"
_err "$response"
return 1
fi
recordid="$(echo "$record" | _egrep_o '<id xsi:type="xsd:int">[0-9]*</id>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug recordid "$recordid"
if [ -z "$recordid" ]; then
_err "can not find record id"
return 1
fi
if ! _dd_soap deleteRecord string zonename "$_domain." int id "$recordid"; then
_err "delete error"
return 1
fi
_contains "$response" "Success"
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
if ! _dd_soap "listZones"; then
return 1
fi
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if _contains "$response" ">$h.</origin>"; 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
}
#method
_dd_soap() {
_method="$1"
shift
_urn="${_method}wsdl"
# put the parameters to xml
body="<tns:$_method>
<apiuser xsi:type=\"xsd:string\">$DD_API_User</apiuser>
<apikey xsi:type=\"xsd:string\">$DD_API_Key</apikey>
"
while [ "$1" ]; do
_t="$1"
shift
_k="$1"
shift
_v="$1"
shift
body="$body<$_k xsi:type=\"xsd:$_t\">$_v</$_k>"
done
body="$body</tns:$_method>"
_debug2 "SOAP request ${body}"
# build SOAP XML
_xml='<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="urn:'$_urn'"
xmlns:types="urn:'$_urn'/encodedTypes"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'"$body"'</soap:Body>
</soap:Envelope>'
_debug2 _xml "$_xml"
# set SOAP headers
_action="SOAPAction: \"urn:$_urn#$_method\""
_debug2 "_action" "$_action"
export _H1="$_action"
export _H2="Content-Type: text/xml; charset=utf-8"
_url="$_DD_BASE/$_method.php"
_debug "_url" "$_url"
if ! response="$(_post "${_xml}" "${_url}")"; then
_err "Error <$1>"
return 1
fi
_debug2 "response" "$response"
response="$(echo "$response" | tr -d "\r\n" | _egrep_o ":${_method}Response .*:${_method}Response><")"
_debug2 "response" "$response"
return 0
}

26
dnsapi/dns_dynu.sh

@ -10,7 +10,7 @@
Dynu_Token="" Dynu_Token=""
# #
#Endpoint #Endpoint
Dynu_EndPoint="https://api.dynu.com/v1"
Dynu_EndPoint="https://api.dynu.com/v2"
# #
#Author: Dynu Systems, Inc. #Author: Dynu Systems, Inc.
#Report Bugs here: https://github.com/shar0119/acme.sh #Report Bugs here: https://github.com/shar0119/acme.sh
@ -51,11 +51,11 @@ dns_dynu_add() {
_debug _domain_name "$_domain_name" _debug _domain_name "$_domain_name"
_info "Creating TXT record." _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
if ! _dynu_rest POST "dns/$dnsId/record" "{\"domainId\":\"$dnsId\",\"nodeName\":\"$_node\",\"recordType\":\"TXT\",\"textData\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then
return 1 return 1
fi fi
if ! _contains "$response" "text_data"; then
if ! _contains "$response" "200"; then
_err "Could not add TXT record." _err "Could not add TXT record."
return 1 return 1
fi fi
@ -132,11 +132,12 @@ _get_root() {
return 1 return 1
fi fi
if ! _dynu_rest GET "dns/get/$h"; then
if ! _dynu_rest GET "dns/getroot/$h"; then
return 1 return 1
fi fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then
dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2)
_domain_name=$h _domain_name=$h
_node=$(printf "%s" "$domain" | cut -d . -f 1-$p) _node=$(printf "%s" "$domain" | cut -d . -f 1-$p)
return 0 return 0
@ -152,7 +153,7 @@ _get_recordid() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then
if ! _dynu_rest GET "dns/$dnsId/record"; then
return 1 return 1
fi fi
@ -161,19 +162,18 @@ _get_recordid() {
return 0 return 0
fi fi
_dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2)
_dns_record_id=$(printf "%s" "$response" | sed -e 's/[^{]*\({[^}]*}\)[^{]*/\1\n/g' | grep "\"textData\":\"$txtvalue\"" | sed -e 's/.*"id":\([^,]*\).*/\1/')
return 0 return 0
} }
_delete_txt_record() { _delete_txt_record() {
_dns_record_id=$1 _dns_record_id=$1
if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then
if ! _dynu_rest DELETE "dns/$dnsId/record/$_dns_record_id"; then
return 1 return 1
fi fi
if ! _contains "$response" "true"; then
if ! _contains "$response" "200"; then
return 1 return 1
fi fi
@ -189,7 +189,7 @@ _dynu_rest() {
export _H1="Authorization: Bearer $Dynu_Token" export _H1="Authorization: Bearer $Dynu_Token"
export _H2="Content-Type: application/json" export _H2="Content-Type: application/json"
if [ "$data" ]; then
if [ "$data" ] || [ "$m" = "DELETE" ]; then
_debug data "$data" _debug data "$data"
response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")" response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")"
else else
@ -216,8 +216,8 @@ _dynu_authentication() {
_err "Authentication failed." _err "Authentication failed."
return 1 return 1
fi 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)
if _contains "$response" "access_token"; then
Dynu_Token=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2)
fi fi
if _contains "$Dynu_Token" "null"; then if _contains "$Dynu_Token" "null"; then
Dynu_Token="" Dynu_Token=""

121
dnsapi/dns_dynv6.sh

@ -0,0 +1,121 @@
#!/usr/bin/env sh
#Author StefanAbl
#Usage specify a private keyfile to use with dynv6 'export KEY="path/to/keyfile"'
#if no keyfile is specified, you will be asked if you want to create one in /home/$USER/.ssh/dynv6 and /home/$USER/.ssh/dynv6.pub
######## Public functions #####################
# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_dynv6_add() {
fulldomain=$1
txtvalue=$2
_info "Using dynv6 api"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_get_keyfile
_info "using keyfile $dynv6_keyfile"
_get_domain "$fulldomain"
_your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)"
if ! _contains "$_your_hosts" "$_host"; then
_debug "The host is $_host and the record $_record"
_debug "Dynv6 returned $_your_hosts"
_err "The host $_host does not exists on your dynv6 account"
return 1
fi
_debug "found host on your account"
returnval="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts \""$_host"\" records set \""$_record"\" txt data \""$txtvalue"\")"
_debug "Dynv6 returend this after record was added: $returnval"
if _contains "$returnval" "created"; then
return 0
elif _contains "$returnval" "updated"; then
return 0
else
_err "Something went wrong! it does not seem like the record was added succesfully"
return 1
fi
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_dynv6_rm() {
fulldomain=$1
txtvalue=$2
_info "Using dynv6 api"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_get_keyfile
_info "using keyfile $dynv6_keyfile"
_get_domain "$fulldomain"
_your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)"
if ! _contains "$_your_hosts" "$_host"; then
_debug "The host is $_host and the record $_record"
_debug "Dynv6 returned $_your_hosts"
_err "The host $_host does not exists on your dynv6 account"
return 1
fi
_debug "found host on your account"
_info "$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts "\"$_host\"" records del "\"$_record\"" txt)"
return 0
}
#################### Private functions below ##################################
#Usage: No Input required
#returns
#dynv6_keyfile the path to the new keyfile that has been generated
_generate_new_key() {
dynv6_keyfile="$(eval echo ~"$USER")/.ssh/dynv6"
_info "Path to key file used: $dynv6_keyfile"
if [ ! -f "$dynv6_keyfile" ] && [ ! -f "$dynv6_keyfile.pub" ]; then
_debug "generating key in $dynv6_keyfile and $dynv6_keyfile.pub"
ssh-keygen -f "$dynv6_keyfile" -t ssh-ed25519 -N ''
else
_err "There is already a file in $dynv6_keyfile or $dynv6_keyfile.pub"
return 1
fi
}
#Usage: _acme-challenge.www.example.dynv6.net
#returns
#_host= example.dynv6.net
#_record=_acme-challenge.www
#aborts if not a valid domain
_get_domain() {
_full_domain="$1"
_debug "getting domain for $_full_domain"
if ! _contains "$_full_domain" 'dynv6.net' && ! _contains "$_full_domain" 'dns.army' && ! _contains "$_full_domain" 'dns.navy'; then
_err "The hosts does not seem to be a dynv6 host"
return 1
fi
_record="${_full_domain%.*}"
_record="${_record%.*}"
_record="${_record%.*}"
_debug "The record we are ging to use is $_record"
_host="$_full_domain"
while [ "$(echo "$_host" | grep -o '\.' | wc -l)" != "2" ]; do
_host="${_host#*.}"
done
_debug "And the host is $_host"
return 0
}
# Usage: No input required
#returns
#dynv6_keyfile path to the key that will be used
_get_keyfile() {
_debug "get keyfile method called"
dynv6_keyfile="${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}"
_debug Your key is "$dynv6_keyfile"
if [ -z "$dynv6_keyfile" ]; then
if [ -z "$KEY" ]; then
_err "You did not specify a key to use with dynv6"
_info "Creating new dynv6 api key to add to dynv6.com"
_generate_new_key
_info "Please add this key to dynv6.com $(cat "$dynv6_keyfile.pub")"
_info "Hit Enter to contiue"
read -r _
#save the credentials to the account conf file.
else
dynv6_keyfile="$KEY"
fi
_saveaccountconf_mutable dynv6_keyfile "$dynv6_keyfile"
fi
}

171
dnsapi/dns_easydns.sh

@ -0,0 +1,171 @@
#!/usr/bin/env sh
#######################################################
#
# easyDNS REST API for acme.sh by Neilpang based on dns_cf.sh
#
# API Documentation: https://sandbox.rest.easydns.net:3001/
#
# Author: wurzelpanzer [wurzelpanzer@maximolider.net]
# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2647
#
#################### Public functions #################
#EASYDNS_Key="xxxxxxxxxxxxxxxxxxxxxxxx"
#EASYDNS_Token="xxxxxxxxxxxxxxxxxxxxxxxx"
EASYDNS_Api="https://rest.easydns.net"
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_easydns_add() {
fulldomain=$1
txtvalue=$2
EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}"
if [ -z "$EASYDNS_Token" ] || [ -z "$EASYDNS_Key" ]; then
_err "You didn't specify an easydns.net token or api key. Signup at https://cp.easydns.com/manage/security/api/signup.php"
return 1
else
_saveaccountconf_mutable EASYDNS_Token "$EASYDNS_Token"
_saveaccountconf_mutable EASYDNS_Key "$EASYDNS_Key"
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"
_EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}"
if ! printf "%s" "$response" | grep \"status\":200 >/dev/null; then
_err "Error"
return 1
fi
_info "Adding record"
if _EASYDNS_rest PUT "zones/records/add/$_domain/TXT" "{\"host\":\"$_sub_domain\",\"rdata\":\"$txtvalue\"}"; then
if _contains "$response" "\"status\":201"; then
_info "Added, OK"
return 0
elif _contains "$response" "Record already exists"; then
_info "Already exists, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
dns_easydns_rm() {
fulldomain=$1
txtvalue=$2
EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_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"
_EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}"
if ! printf "%s" "$response" | grep \"status\":200 >/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 ! _EASYDNS_rest DELETE "zones/records/$_domain/$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
_get_root() {
domain=$1
i=1
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 ! _EASYDNS_rest GET "zones/records/all/$h"; then
return 1
fi
if _contains "$response" "\"status\":200"; 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
}
_EASYDNS_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
basicauth=$(printf "%s" "$EASYDNS_Token":"$EASYDNS_Key" | _base64)
export _H1="accept: application/json"
if [ "$basicauth" ]; then
export _H2="Authorization: Basic $basicauth"
fi
if [ "$m" != "GET" ]; then
export _H3="Content-Type: application/json"
_debug data "$data"
response="$(_post "$data" "$EASYDNS_Api/$ep" "" "$m")"
else
response="$(_get "$EASYDNS_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

358
dnsapi/dns_euserv.sh

@ -0,0 +1,358 @@
#!/usr/bin/env sh
#This is the euserv.eu api wrapper for acme.sh
#
#Author: Michael Brueckner
#Report Bugs: https://www.github.com/initit/acme.sh or mbr@initit.de
#
#EUSERV_Username="username"
#
#EUSERV_Password="password"
#
# Dependencies:
# -------------
# - none -
EUSERV_Api="https://api.euserv.net"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_euserv_add() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}"
EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}"
if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then
EUSERV_Username=""
EUSERV_Password=""
_err "You don't specify euserv user and password yet."
_err "Please create your key and try again."
return 1
fi
#save the user and email to the account conf file.
_saveaccountconf_mutable EUSERV_Username "$EUSERV_Username"
_saveaccountconf_mutable EUSERV_Password "$EUSERV_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"
if ! _euserv_add_record "$_domain" "$_sub_domain" "$txtvalue"; then
return 1
fi
}
#fulldomain txtvalue
dns_euserv_rm() {
fulldomain="$(echo "$1" | _lower_case)"
txtvalue=$2
EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}"
EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}"
if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then
EUSERV_Username=""
EUSERV_Password=""
_err "You don't specify euserv user and password yet."
_err "Please create your key and try again."
return 1
fi
#save the user and email to the account conf file.
_saveaccountconf_mutable EUSERV_Username "$EUSERV_Username"
_saveaccountconf_mutable EUSERV_Password "$EUSERV_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>domain.dns_get_active_records</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>login</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>password</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>domain_id</name>
<value>
<int>%s</int>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id")
export _H1="Content-Type: text/xml"
response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
_err "Error could not get txt records"
_debug "xml_content" "$xml_content"
_debug "response" "$response"
return 1
fi
if ! echo "$response" | grep '>dns_record_content<.*>'"$txtvalue"'<' >/dev/null; then
_info "Do not need to delete record"
else
# find XML block where txtvalue is in. The record_id is allways prior this line!
_endLine=$(echo "$response" | grep -n '>dns_record_content<.*>'"$txtvalue"'<' | cut -d ':' -f 1)
# record_id is the last <name> Tag with a number before the row _endLine, identified by </name><value><struct>
_record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '</name><value><struct>' | _tail_n 1 | sed 's/.*<name>\([0-9]*\)<\/name>.*/\1/')
_info "Deleting record"
_euserv_delete_record "$_record_id"
fi
}
#################### Private functions below ##################################
_get_root() {
domain=$1
_debug "get root"
# Just to read the domain_orders once
domain=$1
i=2
p=1
if ! _euserv_get_domain_orders; then
return 1
fi
# Get saved response with domain_orders
response="$_euserv_domain_orders"
while true; do
h=$(echo "$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=$(echo "$domain" | cut -d . -f 1-$p)
_domain="$h"
if ! _euserv_get_domain_id "$_domain"; then
_err "invalid domain"
return 1
fi
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_euserv_get_domain_orders() {
# returns: _euserv_domain_orders
_debug "get domain_orders"
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>domain.get_domain_orders</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>login</name>
<value><string>%s</string></value>
</member>
<member>
<name>password</name>
<value><string>%s</string></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$EUSERV_Username" "$EUSERV_Password")
export _H1="Content-Type: text/xml"
response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
_err "Error could not get domain orders"
_debug "xml_content" "$xml_content"
_debug "response" "$response"
return 1
fi
# save response to reduce API calls
_euserv_domain_orders="$response"
return 0
}
_euserv_get_domain_id() {
# returns: _euserv_domain_id
domain=$1
_debug "get domain_id"
# find line where the domain name is within the $response
_startLine=$(echo "$_euserv_domain_orders" | grep -n '>domain_name<.*>'"$domain"'<' | cut -d ':' -f 1)
# next occurency of domain_id after the domain_name is the correct one
_euserv_domain_id=$(echo "$_euserv_domain_orders" | sed -n "$_startLine"',$p' | grep '>domain_id<' | _head_n 1 | sed 's/.*<i4>\([0-9]*\)<\/i4>.*/\1/')
if [ -z "$_euserv_domain_id" ]; then
_err "Could not find domain_id for domain $domain"
_debug "_euserv_domain_orders" "$_euserv_domain_orders"
return 1
fi
return 0
}
_euserv_delete_record() {
record_id=$1
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>domain.dns_delete_record</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>login</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>password</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>dns_record_id</name>
<value>
<int>%s</int>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$record_id")
export _H1="Content-Type: text/xml"
response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
_err "Error deleting record"
_debug "xml_content" "$xml_content"
_debug "response" "$response"
return 1
fi
return 0
}
_euserv_add_record() {
domain=$1
sub_domain=$2
txtval=$3
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>domain.dns_create_record</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>login</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>password</name>
<value>
<string>%s</string></value>
</member>
<member>
<name>domain_id</name>
<value>
<int>%s</int>
</value>
</member>
<member>
<name>dns_record_subdomain</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>dns_record_type</name>
<value>
<string>TXT</string>
</value>
</member>
<member>
<name>dns_record_value</name>
<value>
<string>%s</string>
</value>
</member>
<member>
<name>dns_record_ttl</name>
<value>
<int>300</int>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id" "$sub_domain" "$txtval")
export _H1="Content-Type: text/xml"
response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
_err "Error could not create record"
_debug "xml_content" "$xml_content"
_debug "response" "$response"
return 1
fi
return 0
}

168
dnsapi/dns_exoscale.sh

@ -0,0 +1,168 @@
#!/usr/bin/env sh
EXOSCALE_API=https://api.exoscale.com/dns/v1
######## Public functions #####################
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_exoscale_add() {
fulldomain=$1
txtvalue=$2
if ! _checkAuth; 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 _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK"
return 0
fi
fi
_err "Add txt record error."
return 1
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_exoscale_rm() {
fulldomain=$1
txtvalue=$2
if ! _checkAuth; 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"
_exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token"
if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then
_record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
fi
if [ -z "$_record_id" ]; then
_err "Can not get record id to remove."
return 1
fi
_debug "Deleting record $_record_id"
if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then
_err "Delete record error."
return 1
fi
return 0
}
#################### Private functions below ##################################
_checkAuth() {
EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}"
EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}"
if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then
EXOSCALE_API_KEY=""
EXOSCALE_SECRET_KEY=""
_err "You don't specify Exoscale application key and application secret yet."
_err "Please create you key and try again."
return 1
fi
_saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY"
_saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY"
return 0
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
# _domain_token=sdjkglgdfewsdfg
_get_root() {
if ! _exoscale_rest GET "domains"; then
return 1
fi
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 _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \")
_domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_token" ] && [ "$_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
}
# returns response
_exoscale_rest() {
method=$1
path="$2"
data="$3"
token="$4"
request_url="$EXOSCALE_API/$path"
_debug "$path"
export _H1="Accept: application/json"
if [ "$token" ]; then
export _H2="X-DNS-Domain-Token: $token"
else
export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY"
fi
if [ "$data" ] || [ "$method" = "DELETE" ]; then
export _H3="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
}

331
dnsapi/dns_freedns.sh

@ -7,6 +7,7 @@
# #
#Author: David Kerr #Author: David Kerr
#Report Bugs here: https://github.com/dkerr64/acme.sh #Report Bugs here: https://github.com/dkerr64/acme.sh
#or here... https://github.com/acmesh-official/acme.sh/issues/2305
# #
######## Public functions ##################### ######## Public functions #####################
@ -46,132 +47,38 @@ dns_freedns_add() {
_saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE"
# We may have to cycle through the domain name to find the
# TLD that we own...
i=1
wmax="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
while [ "$i" -lt "$wmax" ]; do
# split our full domain name into two parts... # 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")" 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
_debug2 htmlpage "$htmlpage"
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")"
_debug2 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
while [ "$i" -lt "$lines" ]; do
i="$(_math "$i" + 1)" 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
else
# lines contain DNS records for all subdomains
DNSname="$(echo "$line" | _egrep_o 'edit.php.*</a>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug2 DNSname "$DNSname"
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 [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then
DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)"
# Now get current value for the TXT record. This method may
# not produce accurate results as the value field is truncated
# on this webpage. To get full value we would need to load
# another page. However we don't really need this so long as
# there is only one TXT record for the acme challenge subdomain.
DNSvalue="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '5p' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug2 DNSvalue "$DNSvalue"
if [ $found != 0 ]; then
top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)"
_debug "sub_domain: $sub_domain"
_debug "top_domain: $top_domain"
DNSdomainid="$(_freedns_domain_id "$top_domain")"
if [ "$?" = "0" ]; then
_info "Domain $top_domain found at FreeDNS, domain_id $DNSdomainid"
break break
# we are breaking out of the loop at the first match of DNS name
# and DNS type (if we are past finding the domainid). This assumes
# that there is only ever one TXT record for the LetsEncrypt/acme
# challenge subdomain. This seems to be a reasonable assumption
# as the acme client deletes the TXT record on successful validation.
fi
else else
DNSname=""
DNStype=""
fi
_info "Domain $top_domain not found at FreeDNS, try with next level of TLD"
fi fi
done done
_debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid"
_debug "DNSvalue: $DNSvalue"
if [ -z "$DNSdomainid" ]; then if [ -z "$DNSdomainid" ]; then
# If domain ID is empty then something went wrong (top level # If domain ID is empty then something went wrong (top level
# domain not found at FreeDNS). # domain not found at FreeDNS).
if [ "$attempts" = "0" ]; then
# exhausted maximum retry attempts
_debug "$htmlpage"
_debug "$subdomain_csv"
_err "Domain $top_domain not found at FreeDNS" _err "Domain $top_domain not found at FreeDNS"
return 1 return 1
fi fi
else
# break out of the 'retry' loop... we have found our domain ID
break
fi
_info "Domain $top_domain not found at FreeDNS"
_info "Retry loading subdomain page ($attempts attempts remaining)"
done
if [ -z "$DNSdataid" ]; then
# If data ID is empty then specific subdomain does not exist yet, need
# to create it this should always be the case as the acme client
# deletes the entry after domain is validated.
# 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" _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
return $? return $?
else
if [ "$txtvalue" = "$DNSvalue" ]; then
# if value in TXT record matches value requested then DNS record
# does not need to be updated. But...
# Testing value match fails. Website is truncating the value field.
# So for now we will always go down the else path. Though in theory
# should never come here anyway as the acme client deletes
# the TXT record on successful validation, so we should not even
# have found a TXT record !!
_info "No update necessary for $fulldomain at FreeDNS"
return 0
else
# Delete the old TXT record (with the wrong value)
if _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"; then
# And add in new TXT record with the value provided
_freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
fi
return $?
fi
fi
return 0
} }
#Usage: fulldomain txtvalue #Usage: fulldomain txtvalue
@ -186,65 +93,48 @@ dns_freedns_rm() {
# Need to read cookie from conf file again in case new value set # Need to read cookie from conf file again in case new value set
# during login to FreeDNS when TXT record was created. # 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")"
FREEDNS_COOKIE="$(_readaccountconf "FREEDNS_COOKIE")"
_debug "FreeDNS login cookies: $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")"
TXTdataid="$(_freedns_data_id "$fulldomain" "TXT")"
if [ "$?" != "0" ]; then if [ "$?" != "0" ]; then
_info "Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS"
return 1 return 1
fi fi
_debug "Data ID's found, $TXTdataid"
subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$fulldomain")"
_debug2 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)"
# now we have one (or more) TXT record data ID's. Load the page
# for that record and search for the record txt value. If match
# then we can delete it.
lines="$(echo "$TXTdataid" | wc -l)"
_debug "Found $lines TXT data records for $fulldomain"
i=0 i=0
found=0
while [ "$i" -lt "$lines" ]; do while [ "$i" -lt "$lines" ]; do
i="$(_math "$i" + 1)" i="$(_math "$i" + 1)"
line="$(echo "$subdomain_csv" | sed -n "${i}p")"
_debug2 line "$line"
DNSname="$(echo "$line" | _egrep_o 'edit.php.*</a>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug2 DNSname "$DNSname"
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 [ "$DNSname" = "$fulldomain" ] && [ "$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)"
_debug2 DNSvalue "$DNSvalue"
# if [ "$DNSvalue" = "$txtvalue" ]; then
# Testing value match fails. Website is truncating the value
# field. So for now we will assume that there is only one TXT
# field for the sub domain and just delete it. Currently this
# is a safe assumption.
_freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"
dataid="$(echo "$TXTdataid" | sed -n "${i}p")"
_debug "$dataid"
htmlpage="$(_freedns_retrieve_data_page "$FREEDNS_COOKIE" "$dataid")"
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
echo "$htmlpage" | grep "value=\"&quot;$txtvalue&quot;\"" >/dev/null
if [ "$?" = "0" ]; then
# Found a match... delete the record and return
_info "Deleting TXT record for $fulldomain, $txtvalue"
_freedns_delete_txt_record "$FREEDNS_COOKIE" "$dataid"
return $? return $?
# fi
fi fi
done done
done
# If we get this far we did not find a match (after two attempts)
# If we get this far we did not find a match
# Not necessarily an error, but log anyway. # Not necessarily an error, but log anyway.
_debug2 "$subdomain_csv"
_info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS"
_info "Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS"
return 0 return 0
} }
@ -272,7 +162,7 @@ _freedns_login() {
# if cookies is not empty then logon successful # if cookies is not empty then logon successful
if [ -z "$cookies" ]; then if [ -z "$cookies" ]; then
_debug "$htmlpage"
_debug3 "htmlpage: $htmlpage"
_err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file"
return 1 return 1
fi fi
@ -301,7 +191,34 @@ _freedns_retrieve_subdomain_page() {
return 1 return 1
fi fi
_debug2 "$htmlpage"
_debug3 "htmlpage: $htmlpage"
printf "%s" "$htmlpage"
return 0
}
# usage _freedns_retrieve_data_page login_cookies data_id
# echo page retrieved (html)
# returns 0 success
_freedns_retrieve_data_page() {
export _H1="Cookie:$1"
export _H2="Accept-Language:en-US"
data_id="$2"
url="https://freedns.afraid.org/subdomain/edit.php?data_id=$2"
_debug "Retrieve data page for ID $data_id from FreeDNS"
htmlpage="$(_get "$url")"
if [ "$?" != "0" ]; then
_err "FreeDNS retrieve data page failed bad RC from _get"
return 1
elif [ -z "$htmlpage" ]; then
_err "FreeDNS returned empty data page"
return 1
fi
_debug3 "htmlpage: $htmlpage"
printf "%s" "$htmlpage" printf "%s" "$htmlpage"
return 0 return 0
@ -315,7 +232,7 @@ _freedns_add_txt_record() {
domain_id="$2" domain_id="$2"
subdomain="$3" subdomain="$3"
value="$(printf '%s' "$4" | _url_encode)" value="$(printf '%s' "$4" | _url_encode)"
url="http://freedns.afraid.org/subdomain/save.php?step=2"
url="https://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")" htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
@ -323,17 +240,17 @@ _freedns_add_txt_record() {
_err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post"
return 1 return 1
elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then
_debug "$htmlpage"
_debug3 "htmlpage: $htmlpage"
_err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file"
return 1 return 1
elif _contains "$htmlpage" "security code was incorrect"; then elif _contains "$htmlpage" "security code was incorrect"; then
_debug "$htmlpage"
_debug3 "htmlpage: $htmlpage"
_err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code" _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" _err "Note that you cannot use automatic DNS validation for FreeDNS public domains"
return 1 return 1
fi fi
_debug2 "$htmlpage"
_debug3 "htmlpage: $htmlpage"
_info "Added acme challenge TXT record for $fulldomain at FreeDNS" _info "Added acme challenge TXT record for $fulldomain at FreeDNS"
return 0 return 0
} }
@ -352,7 +269,7 @@ _freedns_delete_txt_record() {
_err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get"
return 1 return 1
elif ! _contains "$htmlheader" "200 OK"; then elif ! _contains "$htmlheader" "200 OK"; then
_debug "$htmlheader"
_debug2 "htmlheader: $htmlheader"
_err "FreeDNS failed to delete TXT record $data_id" _err "FreeDNS failed to delete TXT record $data_id"
return 1 return 1
fi fi
@ -360,3 +277,95 @@ _freedns_delete_txt_record() {
_info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS"
return 0 return 0
} }
# usage _freedns_domain_id domain_name
# echo the domain_id if found
# return 0 success
_freedns_domain_id() {
# Start by escaping the dots in the domain name
search_domain="$(echo "$1" | sed 's/\./\\./g')"
# 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
domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
| grep "<td>$search_domain</td>\|<td>$search_domain(.*)</td>" \
| sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' \
| cut -d = -f 2)"
# The above beauty extracts domain ID from the html page...
# strip out all blank space and new lines. Then insert newlines
# before each table row <tr>
# search for the domain within each row (which may or may not have
# a text string in brackets (.*) after it.
# And finally extract the domain ID.
if [ -n "$domain_id" ]; then
printf "%s" "$domain_id"
return 0
fi
_debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
done
_debug "Domain $search_domain not found after retry"
return 1
}
# usage _freedns_data_id domain_name record_type
# echo the data_id(s) if found
# return 0 success
_freedns_data_id() {
# Start by escaping the dots in the domain name
search_domain="$(echo "$1" | sed 's/\./\\./g')"
record_type="$2"
# 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
data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/<tr>/@<tr>/g' | tr '@' '\n' \
| grep "<td[a-zA-Z=#]*>$record_type</td>" \
| grep "<ahref.*>$search_domain</a>" \
| sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' \
| cut -d = -f 2)"
# The above beauty extracts data ID from the html page...
# strip out all blank space and new lines. Then insert newlines
# before each table row <tr>
# search for the record type withing each row (e.g. TXT)
# search for the domain within each row (which is within a <a..>
# </a> anchor. And finally extract the domain ID.
if [ -n "$data_id" ]; then
printf "%s" "$data_id"
return 0
fi
_debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)"
done
_debug "Domain $search_domain not found after retry"
return 1
}

62
dnsapi/dns_gandi_livedns.sh

@ -7,6 +7,7 @@
# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable
# #
#Author: Frédéric Crozat <fcrozat@suse.com> #Author: Frédéric Crozat <fcrozat@suse.com>
# Dominik Röttsches <drott@google.com>
#Report Bugs here: https://github.com/fcrozat/acme.sh #Report Bugs here: https://github.com/fcrozat/acme.sh
# #
######## Public functions ##################### ######## Public functions #####################
@ -36,9 +37,7 @@ dns_gandi_livedns_add() {
_debug domain "$_domain" _debug domain "$_domain"
_debug sub_domain "$_sub_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")"
_dns_gandi_append_record "$_domain" "$_sub_domain" "$txtvalue"
} }
#Usage: fulldomain txtvalue #Usage: fulldomain txtvalue
@ -56,9 +55,23 @@ dns_gandi_livedns_rm() {
_debug fulldomain "$fulldomain" _debug fulldomain "$fulldomain"
_debug domain "$_domain" _debug domain "$_domain"
_debug sub_domain "$_sub_domain" _debug sub_domain "$_sub_domain"
_debug txtvalue "$txtvalue"
_gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" ""
if ! _dns_gandi_existing_rrset_values "$_domain" "$_sub_domain"; then
return 1
fi
_new_rrset_values=$(echo "$_rrset_values" | sed "s/...$txtvalue...//g")
# Cleanup dangling commata.
_new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, ,/ ,/g")
_new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, *\]/\]/g")
_new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g")
_debug "New rrset_values" "$_new_rrset_values"
_gandi_livedns_rest PUT \
"domains/$_domain/records/$_sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" \
&& _contains "$response" '{"message": "DNS Record Created"}' \
&& _info "Removing record $(__green "success")"
} }
#################### Private functions below ################################## #################### Private functions below ##################################
@ -98,6 +111,45 @@ _get_root() {
return 1 return 1
} }
_dns_gandi_append_record() {
domain=$1
sub_domain=$2
txtvalue=$3
if _dns_gandi_existing_rrset_values "$domain" "$sub_domain"; then
_debug "Appending new value"
_rrset_values=$(echo "$_rrset_values" | sed "s/\"]/\",\"$txtvalue\"]/")
else
_debug "Creating new record" "$_rrset_values"
_rrset_values="[\"$txtvalue\"]"
fi
_debug new_rrset_values "$_rrset_values"
_gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \
"{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" \
&& _contains "$response" '{"message": "DNS Record Created"}' \
&& _info "Adding record $(__green "success")"
}
_dns_gandi_existing_rrset_values() {
domain=$1
sub_domain=$2
if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then
return 1
fi
if ! _contains "$response" '"rrset_type": "TXT"'; then
_debug "Does not have a _acme-challenge TXT record yet."
return 1
fi
if _contains "$response" '"rrset_values": \[\]'; then
_debug "Empty rrset_values for TXT record, no previous TXT record."
return 1
fi
_debug "Already has TXT record."
_rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' \
| _egrep_o '\[".*\"]')
return 0
}
_gandi_livedns_rest() { _gandi_livedns_rest() {
m=$1 m=$1
ep="$2" ep="$2"

167
dnsapi/dns_gcloud.sh

@ -0,0 +1,167 @@
#!/usr/bin/env sh
# Author: Janos Lenart <janos@lenart.io>
######## Public functions #####################
# Usage: dns_gcloud_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gcloud_add() {
fulldomain=$1
txtvalue=$2
_info "Using gcloud"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_dns_gcloud_find_zone || return $?
# Add an extra RR
_dns_gcloud_start_tr || return $?
_dns_gcloud_get_rrdatas || return $?
echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
_dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
}
# Usage: dns_gcloud_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Remove the txt record after validation.
dns_gcloud_rm() {
fulldomain=$1
txtvalue=$2
_info "Using gcloud"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_dns_gcloud_find_zone || return $?
# Remove one RR
_dns_gcloud_start_tr || return $?
_dns_gcloud_get_rrdatas || return $?
echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
_dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
}
#################### Private functions below ##################################
_dns_gcloud_start_tr() {
if ! trd=$(mktemp -d); then
_err "_dns_gcloud_start_tr: failed to create temporary directory"
return 1
fi
tr="$trd/tr.yaml"
_debug tr "$tr"
if ! gcloud dns record-sets transaction start \
--transaction-file="$tr" \
--zone="$managedZone"; then
rm -r "$trd"
_err "_dns_gcloud_start_tr: failed to execute transaction"
return 1
fi
}
_dns_gcloud_execute_tr() {
if ! gcloud dns record-sets transaction execute \
--transaction-file="$tr" \
--zone="$managedZone"; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_execute_tr: failed to execute transaction"
return 1
fi
rm -r "$trd"
for i in $(seq 1 120); do
if gcloud dns record-sets changes list \
--zone="$managedZone" \
--filter='status != done' \
| grep -q '^.*'; then
_info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
sleep 5
else
return 0
fi
done
_err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
rm -r "$trd"
return 1
}
_dns_gcloud_remove_rrs() {
if ! xargs -r gcloud dns record-sets transaction remove \
--name="$fulldomain." \
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr"; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_remove_rrs: failed to remove RRs"
return 1
fi
}
_dns_gcloud_add_rrs() {
ttl=60
if ! xargs -r gcloud dns record-sets transaction add \
--name="$fulldomain." \
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr"; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_add_rrs: failed to add RRs"
return 1
fi
}
_dns_gcloud_find_zone() {
# Prepare a filter that matches zones that are suiteable for this entry.
# For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
# this function finds the longest postfix that has a managed zone.
part="$fulldomain"
filter="dnsName=( "
while [ "$part" != "" ]; do
filter="$filter$part. "
part="$(echo "$part" | sed 's/[^.]*\.*//')"
done
filter="$filter) AND visibility=public"
_debug filter "$filter"
# List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)
if ! match=$(gcloud dns managed-zones list \
--format="value(name, dnsName)" \
--filter="$filter" \
| while read -r dnsName name; do
printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name"
done \
| sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
_err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
return 1
fi
dnsName=$(echo "$match" | cut -f2)
_debug dnsName "$dnsName"
managedZone=$(echo "$match" | cut -f1)
_debug managedZone "$managedZone"
}
_dns_gcloud_get_rrdatas() {
if ! rrdatas=$(gcloud dns record-sets list \
--zone="$managedZone" \
--name="$fulldomain." \
--type=TXT \
--format="value(ttl,rrdatas)"); then
_err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
rm -r "$trd"
return 1
fi
ttl=$(echo "$rrdatas" | cut -f1)
rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
}

79
dnsapi/dns_gd.sh

@ -15,6 +15,8 @@ dns_gd_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}"
GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}"
if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then
GD_Key="" GD_Key=""
GD_Secret="" GD_Secret=""
@ -24,8 +26,8 @@ dns_gd_add() {
fi fi
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf GD_Key "$GD_Key"
_saveaccountconf GD_Secret "$GD_Secret"
_saveaccountconf_mutable GD_Key "$GD_Key"
_saveaccountconf_mutable GD_Secret "$GD_Secret"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -36,27 +38,80 @@ dns_gd_add() {
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_debug "Getting existing records"
if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then
return 1
fi
if _contains "$response" "$txtvalue"; then
_info "The record is existing, skip"
return 0
fi
_add_data="{\"data\":\"$txtvalue\"}"
for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do
_debug2 t "$t"
if [ "$t" ]; then
_add_data="$_add_data,{\"data\":$t}"
fi
done
_debug2 _add_data "$_add_data"
_info "Adding record" _info "Adding record"
if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[{\"data\":\"$txtvalue\"}]"; then
if [ "$response" = "{}" ]; then
if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
_info "Added, sleeping 10 seconds" _info "Added, sleeping 10 seconds"
_sleep 10 _sleep 10
#todo: check if the record takes effect #todo: check if the record takes effect
return 0 return 0
else
_err "Add txt record error."
_err "$response"
return 1
fi
fi fi
_err "Add txt record error." _err "Add txt record error."
return 1
} }
#fulldomain #fulldomain
dns_gd_rm() { dns_gd_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2
GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}"
GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting existing records"
if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then
return 1
fi
if ! _contains "$response" "$txtvalue"; then
_info "The record is not existing, skip"
return 0
fi
_add_data=""
for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do
_debug2 t "$t"
if [ "$t" ] && [ "$t" != "\"$txtvalue\"" ]; then
if [ "$_add_data" ]; then
_add_data="$_add_data,{\"data\":$t}"
else
_add_data="{\"data\":$t}"
fi
fi
done
if [ -z "$_add_data" ]; then
_add_data="{\"data\":\"\"}"
fi
_debug2 _add_data "$_add_data"
_gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"
} }
#################### Private functions below ################################## #################### Private functions below ##################################
@ -113,5 +168,9 @@ _gd_rest() {
return 1 return 1
fi fi
_debug2 response "$response" _debug2 response "$response"
if _contains "$response" "UNABLE_TO_AUTHENTICATE"; then
_err "It seems that your api key or secret is not correct."
return 1
fi
return 0 return 0
} }

168
dnsapi/dns_gdnsdk.sh

@ -0,0 +1,168 @@
#!/usr/bin/env sh
#Author: Herman Sletteng
#Report Bugs here: https://github.com/loial/acme.sh
#
#
# Note, gratisdns requires a login first, so the script needs to handle
# temporary cookies. Since acme.sh _get/_post currently don't directly support
# cookies, I've defined wrapper functions _myget/_mypost to set the headers
GDNSDK_API="https://admin.gratisdns.com"
######## Public functions #####################
#Usage: dns_gdnsdk_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gdnsdk_add() {
fulldomain=$1
txtvalue=$2
_info "Using gratisdns.dk"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _gratisdns_login; then
_err "Login failed!"
return 1
fi
#finding domain zone
if ! _get_domain; then
_err "No matching root domain for $fulldomain found"
return 1
fi
# adding entry
_info "Adding the entry"
_mypost "action=dns_primary_record_added_txt&user_domain=$_domain&name=$fulldomain&txtdata=$txtvalue&ttl=1"
if _successful_update; then return 0; fi
_err "Couldn't create entry!"
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_gdnsdk_rm() {
fulldomain=$1
txtvalue=$2
_info "Using gratisdns.dk"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _gratisdns_login; then
_err "Login failed!"
return 1
fi
if ! _get_domain; then
_err "No matching root domain for $fulldomain found"
return 1
fi
_findentry "$fulldomain" "$txtvalue"
if [ -z "$_id" ]; then
_info "Entry doesn't exist, nothing to delete"
return 0
fi
_debug "Deleting record..."
_mypost "action=dns_primary_delete_txt&user_domain=$_domain&id=$_id"
# removing entry
if _successful_update; then return 0; fi
_err "Couldn't delete entry!"
return 1
}
#################### Private functions below ##################################
_checkcredentials() {
GDNSDK_Username="${GDNSDK_Username:-$(_readaccountconf_mutable GDNSDK_Username)}"
GDNSDK_Password="${GDNSDK_Password:-$(_readaccountconf_mutable GDNSDK_Password)}"
if [ -z "$GDNSDK_Username" ] || [ -z "$GDNSDK_Password" ]; then
GDNSDK_Username=""
GDNSDK_Password=""
_err "You haven't specified gratisdns.dk username and password yet."
_err "Please add credentials and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable GDNSDK_Username "$GDNSDK_Username"
_saveaccountconf_mutable GDNSDK_Password "$GDNSDK_Password"
return 0
}
_checkcookie() {
GDNSDK_Cookie="${GDNSDK_Cookie:-$(_readaccountconf_mutable GDNSDK_Cookie)}"
if [ -z "$GDNSDK_Cookie" ]; then
_debug "No cached cookie found"
return 1
fi
_myget "action="
if (echo "$_result" | grep -q "logmeout"); then
_debug "Cached cookie still valid"
return 0
fi
_debug "Cached cookie no longer valid"
GDNSDK_Cookie=""
_saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
return 1
}
_gratisdns_login() {
if ! _checkcredentials; then return 1; fi
if _checkcookie; then
_debug "Already logged in"
return 0
fi
_debug "Logging into GratisDNS with user $GDNSDK_Username"
if ! _mypost "login=$GDNSDK_Username&password=$GDNSDK_Password&action=logmein"; then
_err "GratisDNS login failed for user $GDNSDK_Username bad RC from _post"
return 1
fi
GDNSDK_Cookie="$(grep -A 15 '302 Found' "$HTTP_HEADER" | _egrep_o 'Cookie: [^;]*' | _head_n 1 | cut -d ' ' -f2)"
if [ -z "$GDNSDK_Cookie" ]; then
_err "GratisDNS login failed for user $GDNSDK_Username. Check $HTTP_HEADER file"
return 1
fi
export GDNSDK_Cookie
_saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
return 0
}
_myget() {
#Adds cookie to request
export _H1="Cookie: $GDNSDK_Cookie"
_result=$(_get "$GDNSDK_API?$1")
}
_mypost() {
#Adds cookie to request
export _H1="Cookie: $GDNSDK_Cookie"
_result=$(_post "$1" "$GDNSDK_API")
}
_get_domain() {
_myget 'action=dns_primarydns'
_domains=$(echo "$_result" | _egrep_o ' domain="[[:alnum:]._-]+' | sed 's/^.*"//')
if [ -z "$_domains" ]; then
_err "Primary domain list not found!"
return 1
fi
for _domain in $_domains; do
if (_endswith "$fulldomain" "$_domain"); then
_debug "Root domain: $_domain"
return 0
fi
done
return 1
}
_successful_update() {
if (echo "$_result" | grep -q 'table-success'); then return 0; fi
return 1
}
_findentry() {
#returns id of dns entry, if it exists
_myget "action=dns_primary_changeDNSsetup&user_domain=$_domain"
_id=$(echo "$_result" | _egrep_o "<td>$1</td>\s*<td>$2</td>[^?]*[^&]*&id=[^&]*" | sed 's/^.*=//')
if [ -n "$_id" ]; then
_debug "Entry found with _id=$_id"
return 0
fi
return 1
}

116
dnsapi/dns_he.sh

@ -19,20 +19,23 @@ dns_he_add() {
_txt_value=$2 _txt_value=$2
_info "Using DNS-01 Hurricane Electric hook" _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 if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then
HE_Username= HE_Username=
HE_Password= HE_Password=
_err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables."
_err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password environment variables."
return 1 return 1
fi fi
_saveaccountconf HE_Username "$HE_Username"
_saveaccountconf HE_Password "$HE_Password"
_saveaccountconf_mutable HE_Username "$HE_Username"
_saveaccountconf_mutable HE_Password "$HE_Password"
# Fills in the $_zone_id # Fills in the $_zone_id
_find_zone "$_full_domain" || return 1 _find_zone "$_full_domain" || return 1
_debug "Zone id \"$_zone_id\" will be used." _debug "Zone id \"$_zone_id\" will be used."
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&account=" body="$body&account="
body="$body&menu=edit_zone" body="$body&menu=edit_zone"
body="$body&Type=TXT" body="$body&Type=TXT"
@ -62,29 +65,36 @@ dns_he_rm() {
_full_domain=$1 _full_domain=$1
_txt_value=$2 _txt_value=$2
_info "Cleaning up after DNS-01 Hurricane Electric hook" _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 # fills in the $_zone_id
_find_zone "$_full_domain" || return 1 _find_zone "$_full_domain" || return 1
_debug "Zone id \"$_zone_id\" will be used." _debug "Zone id \"$_zone_id\" will be used."
# Find the record id to clean # Find the record id to clean
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&hosted_dns_zoneid=$_zone_id" body="$body&hosted_dns_zoneid=$_zone_id"
body="$body&menu=edit_zone" body="$body&menu=edit_zone"
body="$body&hosted_dns_editzone=" body="$body&hosted_dns_editzone="
domain_regex="$(echo "$_full_domain" | sed 's/\./\\./g')" # escape dots
_record_id=$(_post "$body" "https://dns.he.net/" \
| tr -d '\n' \
| _egrep_o "data=\"&quot;${_txt_value}&quot;([^>]+>){6}[^<]+<[^;]+;deleteRecord\('[0-9]+','${domain_regex}','TXT'\)" \
| _egrep_o "[0-9]+','${domain_regex}','TXT'\)$" \
| _egrep_o "^[0-9]+"
)
# The series of egreps above could have been done a bit shorter but
# I wanted to double-check whether it's the correct record (in case
# HE changes their website somehow).
response="$(_post "$body" "https://dns.he.net/")"
_debug2 "response" "$response"
if ! _contains "$response" "$_txt_value"; then
_debug "The txt record is not found, just skip"
return 0
fi
_record_id="$(echo "$response" | tr -d "#" | sed "s/<tr/#<tr/g" | tr -d "\n" | tr "#" "\n" | grep "$_full_domain" | grep '"dns_tr"' | grep "$_txt_value" | cut -d '"' -f 4)"
_debug2 _record_id "$_record_id"
if [ -z "$_record_id" ]; then
_err "Can not find record id"
return 1
fi
# Remove the record # Remove the record
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&menu=edit_zone" body="$body&menu=edit_zone"
body="$body&hosted_dns_zoneid=$_zone_id" body="$body&hosted_dns_zoneid=$_zone_id"
body="$body&hosted_dns_recordid=$_record_id" body="$body&hosted_dns_recordid=$_record_id"
@ -105,41 +115,32 @@ dns_he_rm() {
########################## PRIVATE FUNCTIONS ########################### ########################## PRIVATE FUNCTIONS ###########################
#-- _find_zone() -------------------------------------------------------
# Returns the most specific zone found in administration interface.
#
# Example:
#
# _find_zone first.second.third.co.uk
#
# ... will return the first zone that exists in admin out of these:
# - "first.second.third.co.uk"
# - "second.third.co.uk"
# - "third.co.uk"
# - "co.uk" <-- unlikely
# - "uk" <-'
#
# (another approach would be something like this:
# https://github.com/hlandau/acme/blob/master/_doc/dns.hook
# - that's better if there are multiple pages. It's so much simpler.
# )
_find_zone() { _find_zone() {
_domain="$1" _domain="$1"
body="email=${HE_Username}&pass=${HE_Password}"
_matches=$(_post "$body" "https://dns.he.net/" \
| _egrep_o "delete_dom.*name=\"[^\"]+\" value=\"[0-9]+"
)
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
response="$(_post "$body" "https://dns.he.net/")"
_debug2 response "$response"
if _contains "$response" '>Incorrect<'; then
_err "Unable to login to dns.he.net please check username and password"
return 1
fi
_table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
_debug2 _table "$_table"
_matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"
_debug2 _matches "$_matches"
# Zone names and zone IDs are in same order # Zone names and zone IDs are in same order
_zone_ids=$(echo "$_matches" | cut -d '"' -f 5)
_zone_names=$(echo "$_matches" | cut -d '"' -f 3)
_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 "These are the zones on this HE account:"
_debug2 "$_zone_names"
_debug2 "_zone_names" "$_zone_names"
_debug2 "And these are their respective IDs:" _debug2 "And these are their respective IDs:"
_debug2 "$_zone_ids"
_debug2 "_zone_ids" "$_zone_ids"
if [ -z "$_zone_names" ] || [ -z "$_zone_ids" ]; then
_err "Can not get zone names."
return 1
fi
# Walk through all possible zone names # Walk through all possible zone names
_strip_counter=1 _strip_counter=1
while true; do while true; do
@ -153,17 +154,14 @@ _find_zone() {
_debug "Looking for zone \"${_attempted_zone}\"" _debug "Looking for zone \"${_attempted_zone}\""
# Take care of "." and only match whole lines. Note that grep -F
# cannot be used because there's no way to make it match whole
# lines.
regex="^$(echo "$_attempted_zone" | sed 's/\./\\./g')$"
line_num=$(echo "$_zone_names" \
| grep -n "$regex" \
| cut -d : -f 1
)
if [ -n "$line_num" ]; then
_zone_id=$(echo "$_zone_ids" | sed "${line_num}q;d")
line_num="$(echo "$_zone_names" | grep -n "^$_attempted_zone\$" | _head_n 1 | cut -d : -f 1)"
_debug2 line_num "$line_num"
if [ "$line_num" ]; then
_zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p")
if [ -z "$_zone_id" ]; then
_err "Can not find zone id."
return 1
fi
_debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"." _debug "Found relevant zone \"$_attempted_zone\" with id \"$_zone_id\" - will be used for domain \"$_domain\"."
return 0 return 0
fi fi

156
dnsapi/dns_hexonet.sh

@ -0,0 +1,156 @@
#!/usr/bin/env sh
#
# Hexonet_Login="username!roleId"
#
# Hexonet_Password="rolePassword"
Hexonet_Api="https://coreapi.1api.net/api/call.cgi"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_hexonet_add() {
fulldomain=$1
txtvalue=$2
Hexonet_Login="${Hexonet_Login:-$(_readaccountconf_mutable Hexonet_Login)}"
Hexonet_Password="${Hexonet_Password:-$(_readaccountconf_mutable Hexonet_Password)}"
if [ -z "$Hexonet_Login" ] || [ -z "$Hexonet_Password" ]; then
Hexonet_Login=""
Hexonet_Password=""
_err "You must export variables: Hexonet_Login and Hexonet_Password"
return 1
fi
if ! _contains "$Hexonet_Login" "!"; then
_err "It seems that the Hexonet_Login=$Hexonet_Login is not a restrivteed user."
_err "Please check and retry."
return 1
fi
#save the username and password to the account conf file.
_saveaccountconf_mutable Hexonet_Login "$Hexonet_Login"
_saveaccountconf_mutable Hexonet_Password "$Hexonet_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"
_hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT"
if ! _contains "$response" "CODE=200"; then
_err "Error"
return 1
fi
_info "Adding record"
if _hexonet_rest "command=UpdateDNSZone&dnszone=${_domain}.&addrr0=${_sub_domain}%20IN%20TXT%20${txtvalue}"; then
if _contains "$response" "CODE=200"; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_hexonet_rm() {
fulldomain=$1
txtvalue=$2
Hexonet_Login="${Hexonet_Login:-$(_readaccountconf_mutable Hexonet_Login)}"
Hexonet_Password="${Hexonet_Password:-$(_readaccountconf_mutable Hexonet_Password)}"
if [ -z "$Hexonet_Login" ] || [ -z "$Hexonet_Password" ]; then
Hexonet_Login=""
Hexonet_Password=""
_err "You must export variables: Hexonet_Login and Hexonet_Password"
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"
_hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${txtvalue}"
if ! _contains "$response" "CODE=200"; then
_err "Error"
return 1
fi
count=$(printf "%s\n" "$response" | _egrep_o "PROPERTY[TOTAL][0]=" | cut -d = -f 2)
_debug count "$count"
if [ "$count" = "0" ]; then
_info "Don't need to remove."
else
if ! _hexonet_rest "&command=UpdateDNSZone&dnszone=${_domain}.&delrr0='${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""; then
_err "Delete record error."
return 1
fi
_contains "$response" "CODE=200"
fi
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
i=1
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 ! _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}."; then
return 1
fi
if _contains "$response" "CODE=200"; 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
}
_hexonet_rest() {
query_params="$1"
_debug "$query_params"
response="$(_get "${Hexonet_Api}?s_login=${Hexonet_Login}&s_pw=${Hexonet_Password}&${query_params}")"
if [ "$?" != "0" ]; then
_err "error $query_params"
return 1
fi
_debug2 response "$response"
return 0
}

178
dnsapi/dns_hostingde.sh

@ -0,0 +1,178 @@
#!/usr/bin/env sh
# hosting.de API
# Values to export:
# export HOSTINGDE_ENDPOINT='https://secure.hosting.de'
# export HOSTINGDE_APIKEY='xxxxx'
######## Public functions #####################
dns_hostingde_add() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: _hostingde_addRecord() '${fulldomain}' '${txtvalue}'"
_hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_addRecord
return $?
}
dns_hostingde_rm() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: _hostingde_removeRecord() '${fulldomain}' '${txtvalue}'"
_hostingde_apiKey && _hostingde_getZoneConfig && _hostingde_removeRecord
return $?
}
#################### own Private functions below ##################################
_hostingde_apiKey() {
HOSTINGDE_APIKEY="${HOSTINGDE_APIKEY:-$(_readaccountconf_mutable HOSTINGDE_APIKEY)}"
HOSTINGDE_ENDPOINT="${HOSTINGDE_ENDPOINT:-$(_readaccountconf_mutable HOSTINGDE_ENDPOINT)}"
if [ -z "$HOSTINGDE_APIKEY" ] || [ -z "$HOSTINGDE_ENDPOINT" ]; then
HOSTINGDE_APIKEY=""
HOSTINGDE_ENDPOINT=""
_err "You haven't specified hosting.de API key or endpoint yet."
_err "Please create your key and try again."
return 1
fi
_saveaccountconf_mutable HOSTINGDE_APIKEY "$HOSTINGDE_APIKEY"
_saveaccountconf_mutable HOSTINGDE_ENDPOINT "$HOSTINGDE_ENDPOINT"
}
_hostingde_parse() {
find="${1}"
if [ "${2}" ]; then
notfind="${2}"
fi
if [ "${notfind}" ]; then
_egrep_o \""${find}\":.*" | grep -v "${notfind}" | cut -d ':' -f 2 | cut -d ',' -f 1 | tr -d ' '
else
_egrep_o \""${find}\":.*" | cut -d ':' -f 2 | cut -d ',' -f 1 | tr -d ' '
fi
}
_hostingde_getZoneConfig() {
_info "Getting ZoneConfig"
curZone="${fulldomain#*.}"
returnCode=1
while _contains "${curZone}" "\\."; do
curData="{\"filter\":{\"field\":\"zoneName\",\"value\":\"${curZone}\"},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind")"
_debug "Calling zoneConfigsFind: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneConfigsFind'"
_debug "Result of zoneConfigsFind: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
returnCode=1
break
fi
if _contains "${curResult}" '"totalEntries": 1'; then
_info "Retrieved zone data."
_debug "Zone data: '${curResult}'"
zoneConfigId=$(echo "${curResult}" | _hostingde_parse "id")
zoneConfigName=$(echo "${curResult}" | _hostingde_parse "name")
zoneConfigType=$(echo "${curResult}" | _hostingde_parse "type" "FindZoneConfigsResult")
zoneConfigExpire=$(echo "${curResult}" | _hostingde_parse "expire")
zoneConfigNegativeTtl=$(echo "${curResult}" | _hostingde_parse "negativeTtl")
zoneConfigRefresh=$(echo "${curResult}" | _hostingde_parse "refresh")
zoneConfigRetry=$(echo "${curResult}" | _hostingde_parse "retry")
zoneConfigTtl=$(echo "${curResult}" | _hostingde_parse "ttl")
zoneConfigDnsServerGroupId=$(echo "${curResult}" | _hostingde_parse "dnsServerGroupId")
zoneConfigEmailAddress=$(echo "${curResult}" | _hostingde_parse "emailAddress")
zoneConfigDnsSecMode=$(echo "${curResult}" | _hostingde_parse "dnsSecMode")
zoneConfigTemplateValues=$(echo "${curResult}" | _hostingde_parse "templateValues")
if [ "$zoneConfigTemplateValues" != "null" ]; then
_debug "Zone is tied to a template."
zoneConfigTemplateValuesTemplateId=$(echo "${curResult}" | _hostingde_parse "templateId")
zoneConfigTemplateValuesTemplateName=$(echo "${curResult}" | _hostingde_parse "templateName")
zoneConfigTemplateValuesTemplateReplacementsIPv4=$(echo "${curResult}" | _hostingde_parse "ipv4Replacement")
zoneConfigTemplateValuesTemplateReplacementsIPv6=$(echo "${curResult}" | _hostingde_parse "ipv6Replacement")
zoneConfigTemplateValuesTemplateReplacementsMailIPv4=$(echo "${curResult}" | _hostingde_parse "mailIpv4Replacement")
zoneConfigTemplateValuesTemplateReplacementsMailIPv6=$(echo "${curResult}" | _hostingde_parse "mailIpv6Replacement")
zoneConfigTemplateValuesTemplateTieToTemplate=$(echo "${curResult}" | _hostingde_parse "tieToTemplate")
zoneConfigTemplateValues="{\"templateId\":${zoneConfigTemplateValuesTemplateId},\"templateName\":${zoneConfigTemplateValuesTemplateName},\"templateReplacements\":{\"ipv4Replacement\":${zoneConfigTemplateValuesTemplateReplacementsIPv4},\"ipv6Replacement\":${zoneConfigTemplateValuesTemplateReplacementsIPv6},\"mailIpv4Replacement\":${zoneConfigTemplateValuesTemplateReplacementsMailIPv4},\"mailIpv6Replacement\":${zoneConfigTemplateValuesTemplateReplacementsMailIPv6}},\"tieToTemplate\":${zoneConfigTemplateValuesTemplateTieToTemplate}}"
_debug "Template values: '{$zoneConfigTemplateValues}'"
fi
if [ "${zoneConfigType}" != "\"NATIVE\"" ]; then
_err "Zone is not native"
returnCode=1
break
fi
_debug "zoneConfigId '${zoneConfigId}'"
returnCode=0
break
fi
curZone="${curZone#*.}"
done
if [ $returnCode -ne 0 ]; then
_info "ZoneEnd reached, Zone ${curZone} not found in hosting.de API"
fi
return $returnCode
}
_hostingde_getZoneStatus() {
_debug "Checking Zone status"
curData="{\"filter\":{\"field\":\"zoneConfigId\",\"value\":${zoneConfigId}},\"limit\":1,\"authToken\":\"${HOSTINGDE_APIKEY}\"}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind")"
_debug "Calling zonesFind '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zonesFind'"
_debug "Result of zonesFind '$curResult'"
zoneStatus=$(echo "${curResult}" | _hostingde_parse "status" "success")
_debug "zoneStatus '${zoneStatus}'"
return 0
}
_hostingde_addRecord() {
_info "Adding record to zone"
_hostingde_getZoneStatus
_debug "Result of zoneStatus: '${zoneStatus}'"
while [ "${zoneStatus}" != "\"active\"" ]; do
_sleep 5
_hostingde_getZoneStatus
_debug "Result of zoneStatus: '${zoneStatus}'"
done
curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":${zoneConfigId},\"name\":${zoneConfigName},\"type\":${zoneConfigType},\"dnsServerGroupId\":${zoneConfigDnsServerGroupId},\"dnsSecMode\":${zoneConfigDnsSecMode},\"emailAddress\":${zoneConfigEmailAddress},\"soaValues\":{\"expire\":${zoneConfigExpire},\"negativeTtl\":${zoneConfigNegativeTtl},\"refresh\":${zoneConfigRefresh},\"retry\":${zoneConfigRetry},\"ttl\":${zoneConfigTtl}},\"templateValues\":${zoneConfigTemplateValues}},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
return 1
fi
return 0
}
_hostingde_removeRecord() {
_info "Removing record from zone"
_hostingde_getZoneStatus
_debug "Result of zoneStatus: '$zoneStatus'"
while [ "$zoneStatus" != "\"active\"" ]; do
_sleep 5
_hostingde_getZoneStatus
_debug "Result of zoneStatus: '$zoneStatus'"
done
curData="{\"authToken\":\"${HOSTINGDE_APIKEY}\",\"zoneConfig\":{\"id\":${zoneConfigId},\"name\":${zoneConfigName},\"type\":${zoneConfigType},\"dnsServerGroupId\":${zoneConfigDnsServerGroupId},\"dnsSecMode\":${zoneConfigDnsSecMode},\"emailAddress\":${zoneConfigEmailAddress},\"soaValues\":{\"expire\":${zoneConfigExpire},\"negativeTtl\":${zoneConfigNegativeTtl},\"refresh\":${zoneConfigRefresh},\"retry\":${zoneConfigRetry},\"ttl\":${zoneConfigTtl}},\"templateValues\":${zoneConfigTemplateValues}},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'"
if _contains "${curResult}" '"status": "error"'; then
if _contains "${curResult}" '"code": 10109'; then
_err "The API-Key is invalid or could not be found"
else
_err "UNKNOWN API ERROR"
fi
return 1
fi
return 0
}

180
dnsapi/dns_internetbs.sh

@ -0,0 +1,180 @@
#!/usr/bin/env sh
#This is the Internet.BS api wrapper for acme.sh
#
#Author: <alexey@nelexa.ru> Ne-Lexa
#Report Bugs here: https://github.com/Ne-Lexa/acme.sh
#INTERNETBS_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
#INTERNETBS_API_PASSWORD="sdfsdfsdfljlbjkljlkjsdfoiwje"
INTERNETBS_API_URL="https://api.internet.bs"
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_internetbs_add() {
fulldomain=$1
txtvalue=$2
INTERNETBS_API_KEY="${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}"
INTERNETBS_API_PASSWORD="${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}"
if [ -z "$INTERNETBS_API_KEY" ] || [ -z "$INTERNETBS_API_PASSWORD" ]; then
INTERNETBS_API_KEY=""
INTERNETBS_API_PASSWORD=""
_err "You didn't specify the INTERNET.BS api key and password yet."
_err "Please create you key and try again."
return 1
fi
_saveaccountconf_mutable INTERNETBS_API_KEY "$INTERNETBS_API_KEY"
_saveaccountconf_mutable INTERNETBS_API_PASSWORD "$INTERNETBS_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"
# https://testapi.internet.bs/Domain/DnsRecord/Add?ApiKey=testapi&Password=testpass&FullRecordName=w3.test-api-domain7.net&Type=CNAME&Value=www.internet.bs%&ResponseFormat=json
if _internetbs_rest POST "Domain/DnsRecord/Add" "FullRecordName=${_sub_domain}.${_domain}&Type=TXT&Value=${txtvalue}&ResponseFormat=json"; then
if ! _contains "$response" "\"status\":\"SUCCESS\""; then
_err "ERROR add TXT record"
_err "$response"
return 1
fi
_info "txt record add success."
return 0
fi
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_internetbs_rm() {
fulldomain=$1
txtvalue=$2
INTERNETBS_API_KEY="${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}"
INTERNETBS_API_PASSWORD="${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}"
if [ -z "$INTERNETBS_API_KEY" ] || [ -z "$INTERNETBS_API_PASSWORD" ]; then
INTERNETBS_API_KEY=""
INTERNETBS_API_PASSWORD=""
_err "You didn't specify the INTERNET.BS api key and password 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 _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
# https://testapi.internet.bs/Domain/DnsRecord/List?ApiKey=testapi&Password=testpass&Domain=test-api-domain7.net&FilterType=CNAME&ResponseFormat=json
_internetbs_rest POST "Domain/DnsRecord/List" "Domain=$_domain&FilterType=TXT&ResponseFormat=json"
if ! _contains "$response" "\"status\":\"SUCCESS\""; then
_err "ERROR list dns records"
_err "$response"
return 1
fi
if _contains "$response" "\name\":\"${_sub_domain}.${_domain}\""; then
_info "txt record find."
# https://testapi.internet.bs/Domain/DnsRecord/Remove?ApiKey=testapi&Password=testpass&FullRecordName=www.test-api-domain7.net&Type=cname&ResponseFormat=json
_internetbs_rest POST "Domain/DnsRecord/Remove" "FullRecordName=${_sub_domain}.${_domain}&Type=TXT&ResponseFormat=json"
if ! _contains "$response" "\"status\":\"SUCCESS\""; then
_err "ERROR remove dns record"
_err "$response"
return 1
fi
_info "txt record deleted success."
return 0
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
# https://testapi.internet.bs/Domain/List?ApiKey=testapi&Password=testpass&CompactList=yes&ResponseFormat=json
if _internetbs_rest POST "Domain/List" "CompactList=yes&ResponseFormat=json"; then
if ! _contains "$response" "\"status\":\"SUCCESS\""; then
_err "ERROR fetch domain list"
_err "$response"
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
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-${p})
_domain=${h}
return 0
fi
p=${i}
i=$(_math "$i" + 1)
done
fi
return 1
}
#Usage: method URI data
_internetbs_rest() {
m="$1"
ep="$2"
data="$3"
url="${INTERNETBS_API_URL}/${ep}"
_debug url "$url"
apiKey="$(printf "%s" "${INTERNETBS_API_KEY}" | _url_encode)"
password="$(printf "%s" "${INTERNETBS_API_PASSWORD}" | _url_encode)"
if [ "$m" = "GET" ]; then
response="$(_get "${url}?ApiKey=${apiKey}&Password=${password}&${data}" | tr -d '\r')"
else
_debug2 data "$data"
response="$(_post "$data" "${url}?ApiKey=${apiKey}&Password=${password}" | tr -d '\r')"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

155
dnsapi/dns_inwx.sh

@ -4,6 +4,10 @@
#INWX_User="username" #INWX_User="username"
# #
#INWX_Password="password" #INWX_Password="password"
#
# Dependencies:
# -------------
# - oathtool (When using 2 Factor Authentication)
INWX_Api="https://api.domrobot.com/xmlrpc/" INWX_Api="https://api.domrobot.com/xmlrpc/"
@ -16,6 +20,7 @@ dns_inwx_add() {
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
INWX_User="" INWX_User=""
INWX_Password="" INWX_Password=""
@ -27,6 +32,11 @@ dns_inwx_add() {
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf_mutable INWX_User "$INWX_User" _saveaccountconf_mutable INWX_User "$INWX_User"
_saveaccountconf_mutable INWX_Password "$INWX_Password" _saveaccountconf_mutable INWX_Password "$INWX_Password"
_saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret"
if ! _inwx_login; then
return 1
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -35,53 +45,9 @@ dns_inwx_add() {
fi fi
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_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 ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
_err "Error could net get txt records"
return 1
fi
if ! printf "%s" "$response" | grep "count" >/dev/null; then
_info "Adding record" _info "Adding record"
_inwx_add_record "$_domain" "$_sub_domain" "$txtvalue" _inwx_add_record "$_domain" "$_sub_domain" "$txtvalue"
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 "Updating record"
_inwx_update_record "$_record_id" "$txtvalue"
fi
} }
@ -93,6 +59,7 @@ dns_inwx_rm() {
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
INWX_User="" INWX_User=""
INWX_Password="" INWX_Password=""
@ -101,9 +68,9 @@ dns_inwx_rm() {
return 1 return 1
fi fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable INWX_User "$INWX_User"
_saveaccountconf_mutable INWX_Password "$INWX_Password"
if ! _inwx_login; then
return 1
fi
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -147,7 +114,7 @@ dns_inwx_rm() {
</methodCall>' "$_domain" "$_sub_domain") </methodCall>' "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then
if ! _contains "$response" "Command completed successfully"; then
_err "Error could not get txt records" _err "Error could not get txt records"
return 1 return 1
fi fi
@ -164,8 +131,42 @@ dns_inwx_rm() {
#################### Private functions below ################################## #################### Private functions below ##################################
_inwx_check_cookie() {
INWX_Cookie="${INWX_Cookie:-$(_readaccountconf_mutable INWX_Cookie)}"
if [ -z "$INWX_Cookie" ]; then
_debug "No cached cookie found"
return 1
fi
_H1="$INWX_Cookie"
export _H1
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>account.info</methodName>
</methodCall>')
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
if _contains "$response" "<member><name>code</name><value><int>1000</int></value></member>"; then
_debug "Cached cookie still valid"
return 0
fi
_debug "Cached cookie no longer valid"
_H1=""
export _H1
INWX_Cookie=""
_saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
return 1
}
_inwx_login() { _inwx_login() {
if _inwx_check_cookie; then
_debug "Already logged in"
return 0
fi
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?> xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall> <methodCall>
<methodName>account.login</methodName> <methodName>account.login</methodName>
@ -189,11 +190,63 @@ _inwx_login() {
</value> </value>
</param> </param>
</params> </params>
</methodCall>' $INWX_User $INWX_Password)
</methodCall>' "$INWX_User" "$INWX_Password")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
_H1=$INWX_Cookie
export _H1
export INWX_Cookie
_saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
if ! _contains "$response" "<member><name>code</name><value><int>1000</int></value></member>"; then
_err "INWX API: Authentication error (username/password correct?)"
return 1
fi
#https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
if _contains "$response" "<member><name>tfa</name><value><string>GOOGLE-AUTH</string></value></member>"; then
if [ -z "$INWX_Shared_Secret" ]; then
_err "INWX API: Mobile TAN detected."
_err "Please define a shared secret."
return 1
fi
if ! _exists oathtool; then
_err "Please install oathtool to use 2 Factor Authentication."
_err ""
return 1
fi
tan="$(oathtool --base32 --totp "${INWX_Shared_Secret}" 2>/dev/null)"
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>account.unlock</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>tan</name>
<value>
<string>%s</string>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$tan")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")" 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 ';')"
if ! _contains "$response" "<member><name>code</name><value><int>1000</int></value></member>"; then
_err "INWX API: Mobile TAN not correct."
return 1
fi
fi
} }
@ -205,8 +258,6 @@ _get_root() {
i=2 i=2
p=1 p=1
_H1=$(_inwx_login)
export _H1
xml_content='<?xml version="1.0" encoding="UTF-8"?> xml_content='<?xml version="1.0" encoding="UTF-8"?>
<methodCall> <methodCall>
<methodName>nameserver.list</methodName> <methodName>nameserver.list</methodName>

4
dnsapi/dns_ispconfig.sh

@ -128,7 +128,7 @@ _ISPC_addTxt() {
curSerial="$(date +%s)" curSerial="$(date +%s)"
curStamp="$(date +'%F %T')" 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}\"" 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}}}"
curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}},\"update_serial\":true}"
curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")"
_debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'" _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'"
_debug "Result of _ISPC_addTxt: '$curResult'" _debug "Result of _ISPC_addTxt: '$curResult'"
@ -160,7 +160,7 @@ _ISPC_rmTxt() {
*) *)
unset IFS unset IFS
_info "Retrieved Record ID." _info "Retrieved Record ID."
curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}"
curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}"
curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
_debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
_debug "Result of _ISPC_rmTxt: '$curResult'" _debug "Result of _ISPC_rmTxt: '$curResult'"

286
dnsapi/dns_jd.sh

@ -0,0 +1,286 @@
#!/usr/bin/env sh
#
#JD_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
#JD_ACCESS_KEY_SECRET="xxxxxxx"
#JD_REGION="cn-north-1"
_JD_ACCOUNT="https://uc.jdcloud.com/account/accesskey"
_JD_PROD="clouddnsservice"
_JD_API="jdcloud-api.com"
_JD_API_VERSION="v1"
_JD_DEFAULT_REGION="cn-north-1"
_JD_HOST="$_JD_PROD.$_JD_API"
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_jd_add() {
fulldomain=$1
txtvalue=$2
JD_ACCESS_KEY_ID="${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}"
JD_ACCESS_KEY_SECRET="${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}"
JD_REGION="${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}"
if [ -z "$JD_ACCESS_KEY_ID" ] || [ -z "$JD_ACCESS_KEY_SECRET" ]; then
JD_ACCESS_KEY_ID=""
JD_ACCESS_KEY_SECRET=""
_err "You haven't specifed the jdcloud api key id or api key secret yet."
_err "Please create your key and try again. see $(__green $_JD_ACCOUNT)"
return 1
fi
_saveaccountconf_mutable JD_ACCESS_KEY_ID "$JD_ACCESS_KEY_ID"
_saveaccountconf_mutable JD_ACCESS_KEY_SECRET "$JD_ACCESS_KEY_SECRET"
if [ -z "$JD_REGION" ]; then
_debug "Using default region: $_JD_DEFAULT_REGION"
JD_REGION="$_JD_DEFAULT_REGION"
else
_saveaccountconf_mutable JD_REGION "$JD_REGION"
fi
_JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION"
_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 getViewTree"
_debug "Adding records"
_addrr="{\"req\":{\"hostRecord\":\"$_sub_domain\",\"hostValue\":\"$txtvalue\",\"ttl\":300,\"type\":\"TXT\",\"viewValue\":-1},\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}"
#_addrr='{"req":{"hostRecord":"xx","hostValue":"\"value4\"","jcloudRes":false,"mxPriority":null,"port":null,"ttl":300,"type":"TXT","weight":null,"viewValue":-1},"regionId":"cn-north-1","domainId":"8824"}'
if jd_rest POST "domain/$_domain_id/RRAdd" "" "$_addrr"; then
_rid="$(echo "$response" | tr '{},' '\n' | grep '"id":' | cut -d : -f 2)"
if [ -z "$_rid" ]; then
_err "Can not find record id from the result."
return 1
fi
_info "TXT record added successfully."
_srid="$(_readdomainconf "JD_CLOUD_RIDS")"
if [ "$_srid" ]; then
_rid="$_srid,$_rid"
fi
_savedomainconf "JD_CLOUD_RIDS" "$_rid"
return 0
fi
return 1
}
dns_jd_rm() {
fulldomain=$1
txtvalue=$2
JD_ACCESS_KEY_ID="${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}"
JD_ACCESS_KEY_SECRET="${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}"
JD_REGION="${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}"
if [ -z "$JD_REGION" ]; then
_debug "Using default region: $_JD_DEFAULT_REGION"
JD_REGION="$_JD_DEFAULT_REGION"
fi
_JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION"
_info "Getting existing records for $fulldomain"
_srid="$(_readdomainconf "JD_CLOUD_RIDS")"
_debug _srid "$_srid"
if [ -z "$_srid" ]; then
_err "Not rid skip"
return 0
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"
_cleardomainconf JD_CLOUD_RIDS
_aws_tmpl_xml="{\"ids\":[$_srid],\"action\":\"del\",\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}"
if jd_rest POST "domain/$_domain_id/RROperate" "" "$_aws_tmpl_xml" && _contains "$response" "\"code\":\"OK\""; then
_info "TXT record deleted successfully."
return 0
fi
return 1
}
#################### Private functions below ##################################
_get_root() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug2 "Checking domain: $h"
if ! jd_rest GET "domain"; then
_err "error get domain list"
return 1
fi
if [ -z "$h" ]; then
#not valid
_err "Invalid domain"
return 1
fi
if _contains "$response" "\"domainName\":\"$h\""; then
hostedzone="$(echo "$response" | tr '{}' '\n' | grep "\"domainName\":\"$h\"")"
_debug hostedzone "$hostedzone"
if [ "$hostedzone" ]; then
_domain_id="$(echo "$hostedzone" | tr ',' '\n' | grep "\"id\":" | cut -d : -f 2)"
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
fi
fi
_err "Can't find domain with id: $h"
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
#method uri qstr data
jd_rest() {
mtd="$1"
ep="$2"
qsr="$3"
data="$4"
_debug mtd "$mtd"
_debug ep "$ep"
_debug qsr "$qsr"
_debug data "$data"
CanonicalURI="/$_JD_BASE_URI/$ep"
_debug2 CanonicalURI "$CanonicalURI"
CanonicalQueryString="$qsr"
_debug2 CanonicalQueryString "$CanonicalQueryString"
RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")"
#RequestDate="20190713T082155Z" ######################################################
_debug2 RequestDate "$RequestDate"
export _H1="X-Jdcloud-Date: $RequestDate"
RequestNonce="2bd0852a-8bae-4087-b2d5-$(_time)"
#RequestNonce="894baff5-72d4-4244-883a-7b2eb51e7fbe" #################################
_debug2 RequestNonce "$RequestNonce"
export _H2="X-Jdcloud-Nonce: $RequestNonce"
if [ "$data" ]; then
CanonicalHeaders="content-type:application/json\n"
SignedHeaders="content-type;"
else
CanonicalHeaders=""
SignedHeaders=""
fi
CanonicalHeaders="${CanonicalHeaders}host:$_JD_HOST\nx-jdcloud-date:$RequestDate\nx-jdcloud-nonce:$RequestNonce\n"
SignedHeaders="${SignedHeaders}host;x-jdcloud-date;x-jdcloud-nonce"
_debug2 CanonicalHeaders "$CanonicalHeaders"
_debug2 SignedHeaders "$SignedHeaders"
Hash="sha256"
RequestPayload="$data"
_debug2 RequestPayload "$RequestPayload"
RequestPayloadHash="$(printf "%s" "$RequestPayload" | _digest "$Hash" hex | _lower_case)"
_debug2 RequestPayloadHash "$RequestPayloadHash"
CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$RequestPayloadHash"
_debug2 CanonicalRequest "$CanonicalRequest"
HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)"
_debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
Algorithm="JDCLOUD2-HMAC-SHA256"
_debug2 Algorithm "$Algorithm"
RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)"
_debug2 RequestDateOnly "$RequestDateOnly"
Region="$JD_REGION"
Service="$_JD_PROD"
CredentialScope="$RequestDateOnly/$Region/$Service/jdcloud2_request"
_debug2 CredentialScope "$CredentialScope"
StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest"
_debug2 StringToSign "$StringToSign"
kSecret="JDCLOUD2$JD_ACCESS_KEY_SECRET"
_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" "jdcloud2_request" | _hmac "$Hash" "$kServiceH" hex)"
_debug2 kSigningH "$kSigningH"
signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"
_debug2 signature "$signature"
Authorization="$Algorithm Credential=$JD_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
_debug2 Authorization "$Authorization"
_H3="Authorization: $Authorization"
_debug _H3 "$_H3"
url="https://$_JD_HOST$CanonicalURI"
if [ "$qsr" ]; then
url="https://$_JD_HOST$CanonicalURI?$qsr"
fi
if [ "$mtd" = "GET" ]; then
response="$(_get "$url")"
else
response="$(_post "$data" "$url" "" "$mtd" "application/json")"
fi
_ret="$?"
_debug2 response "$response"
if [ "$_ret" = "0" ]; then
if _contains "$response" "\"error\""; then
_err "Response error:$response"
return 1
fi
fi
return "$_ret"
}

129
dnsapi/dns_joker.sh

@ -0,0 +1,129 @@
#!/usr/bin/env sh
# Joker.com API for acme.sh
#
# This script adds the necessary TXT record to a domain in Joker.com.
#
# You must activate Dynamic DNS in Joker.com DNS configuration first.
# Username and password below refer to Dynamic DNS authentication,
# not your Joker.com login credentials.
# See: https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
#
# NOTE: This script does not support wildcard certificates, because
# Joker.com API does not support adding two TXT records with the same
# subdomain. Adding the second record will overwrite the first one.
# See: https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html
# "... this request will replace all TXT records for the specified
# label by the provided content"
#
# Author: aattww (https://github.com/aattww/)
#
# Report bugs to https://github.com/acmesh-official/acme.sh/issues/2840
#
# JOKER_USERNAME="xxxx"
# JOKER_PASSWORD="xxxx"
JOKER_API="https://svc.joker.com/nic/replace"
######## Public functions #####################
#Usage: dns_joker_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_joker_add() {
fulldomain=$1
txtvalue=$2
JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
if [ -z "$JOKER_USERNAME" ] || [ -z "$JOKER_PASSWORD" ]; then
_err "No Joker.com username and password specified."
return 1
fi
_saveaccountconf_mutable JOKER_USERNAME "$JOKER_USERNAME"
_saveaccountconf_mutable JOKER_PASSWORD "$JOKER_PASSWORD"
if ! _get_root "$fulldomain"; then
_err "Invalid domain"
return 1
fi
_info "Adding TXT record"
if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then
if _startswith "$response" "OK"; then
_info "Added, OK"
return 0
fi
fi
_err "Error adding TXT record."
return 1
}
#fulldomain txtvalue
dns_joker_rm() {
fulldomain=$1
txtvalue=$2
JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
if ! _get_root "$fulldomain"; then
_err "Invalid domain"
return 1
fi
_info "Removing TXT record"
# TXT record is removed by setting its value to empty.
if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then
if _startswith "$response" "OK"; then
_info "Removed, OK"
return 0
fi
fi
_err "Error removing TXT record."
return 1
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
fulldomain=$1
i=1
while true; do
h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
return 1
fi
# Try to remove a test record. With correct root domain, username and password this will return "OK: ..." regardless
# of record in question existing or not.
if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value="; then
if _startswith "$response" "OK"; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
_domain=$h
return 0
fi
fi
i=$(_math "$i" + 1)
done
_debug "Root domain not found"
return 1
}
_joker_rest() {
data="$1"
_debug data "$data"
if ! response="$(_post "$data" "$JOKER_API" "" "POST")"; then
_err "Error POSTing"
return 1
fi
_debug response "$response"
return 0
}

168
dnsapi/dns_kas.sh

@ -0,0 +1,168 @@
#!/usr/bin/env sh
########################################################################
# All-inkl Kasserver hook script for acme.sh
#
# Environment variables:
#
# - $KAS_Login (Kasserver API login name)
# - $KAS_Authtype (Kasserver API auth type. Default: sha1)
# - $KAS_Authdata (Kasserver API auth data.)
#
# Author: Martin Kammerlander, Phlegx Systems OG <martin.kammerlander@phlegx.com>
# Updated by: Marc-Oliver Lange <git@die-lang.es>
# Credits: Inspired by dns_he.sh. Thanks a lot man!
# Git repo: https://github.com/phlegx/acme.sh
# TODO: Better Error handling
########################################################################
KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
######## Public functions #####################
dns_kas_add() {
_fulldomain=$1
_txtvalue=$2
_info "Using DNS-01 All-inkl/Kasserver hook"
_info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
_info "Check and Save Props"
_check_and_save
_info "Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
_info "Getting Record ID"
_get_record_id
_info "Creating TXT DNS record"
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&var1=record_name"
params="$params&wert1=$_record_name"
params="$params&var2=record_type"
params="$params&wert2=TXT"
params="$params&var3=record_data"
params="$params&wert3=$_txtvalue"
params="$params&var4=record_aux"
params="$params&wert4=0"
params="$params&kas_action=add_dns_settings"
params="$params&var5=zone_host"
params="$params&wert5=$_zone"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
if ! _contains "$response" "TRUE"; then
_err "An unkown error occurred, please check manually."
return 1
fi
return 0
}
dns_kas_rm() {
_fulldomain=$1
_txtvalue=$2
_info "Using DNS-01 All-inkl/Kasserver hook"
_info "Cleaning up after All-inkl/Kasserver hook"
_info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
_info "Check and Save Props"
_check_and_save
_info "Checking Zone and Record_Name"
_get_zone_and_record_name "$_fulldomain"
_info "Getting Record ID"
_get_record_id
# If there is a record_id, delete the entry
if [ -n "$_record_id" ]; then
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=delete_dns_settings"
for i in $_record_id; do
params2="$params&var1=record_id"
params2="$params2&wert1=$i"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params2")"
_debug2 "response" "$response"
if ! _contains "$response" "TRUE"; then
_err "Either the txt record is not found or another error occurred, please check manually."
return 1
fi
done
else # Cannot delete or unkown error
_err "No record_id found that can be deleted. Please check manually."
return 1
fi
return 0
}
########################## PRIVATE FUNCTIONS ###########################
# Checks for the ENV variables and saves them
_check_and_save() {
KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
KAS_Authtype="${KAS_Authtype:-$(_readaccountconf_mutable KAS_Authtype)}"
KAS_Authdata="${KAS_Authdata:-$(_readaccountconf_mutable KAS_Authdata)}"
if [ -z "$KAS_Login" ] || [ -z "$KAS_Authtype" ] || [ -z "$KAS_Authdata" ]; then
KAS_Login=
KAS_Authtype=
KAS_Authdata=
_err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
return 1
fi
_saveaccountconf_mutable KAS_Login "$KAS_Login"
_saveaccountconf_mutable KAS_Authtype "$KAS_Authtype"
_saveaccountconf_mutable KAS_Authdata "$KAS_Authdata"
return 0
}
# Gets back the base domain/zone and record name.
# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
_get_zone_and_record_name() {
params="?kas_login=$KAS_Login"
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=get_domains"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
_zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
_domain="$1"
_temp_domain="$(echo "$1" | sed 's/\.$//')"
_rootzone="$_domain"
for i in $_zonen; do
l1=${#_rootzone}
l2=${#i}
if _endswith "$_domain" "$i" && [ "$l1" -ge "$l2" ]; then
_rootzone="$i"
fi
done
_zone="${_rootzone}."
_temp_record_name="$(echo "$_temp_domain" | sed "s/$_rootzone//g")"
_record_name="$(echo "$_temp_record_name" | sed 's/\.$//')"
_debug2 "Zone:" "$_zone"
_debug2 "Domain:" "$_domain"
_debug2 "Record_Name:" "$_record_name"
return 0
}
# Retrieve the DNS record ID
_get_record_id() {
params="?kas_login=$KAS_Login"
params="$params&kas_auth_type=$KAS_Authtype"
params="$params&kas_auth_data=$KAS_Authdata"
params="$params&kas_action=get_dns_settings"
params="$params&var1=zone_host"
params="$params&wert1=$_zone"
_debug2 "Wait for 10 seconds by default before calling KAS API."
_sleep 10
response="$(_get "$KAS_Api$params")"
_debug2 "response" "$response"
_record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
_debug2 _record_id "$_record_id"
return 0
}

107
dnsapi/dns_kinghost.sh

@ -0,0 +1,107 @@
#!/usr/bin/env sh
############################################################
# KingHost API support #
# http://api.kinghost.net/doc/ #
# #
# Author: Felipe Keller Braz <felipebraz@kinghost.com.br> #
# Report Bugs here: https://github.com/kinghost/acme.sh #
# #
# Values to export: #
# export KINGHOST_Username="email@provider.com" #
# export KINGHOST_Password="xxxxxxxxxx" #
############################################################
KING_Api="https://api.kinghost.net/acme"
# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Used to add txt record
dns_kinghost_add() {
fulldomain=$1
txtvalue=$2
KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
if [ -z "$KINGHOST_Username" ] || [ -z "$KINGHOST_Password" ]; then
KINGHOST_Username=""
KINGHOST_Password=""
_err "You don't specify KingHost api password and email yet."
_err "Please create you key and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable KINGHOST_Username "$KINGHOST_Username"
_saveaccountconf_mutable KINGHOST_Password "$KINGHOST_Password"
_debug "Getting txt records"
_kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue"
#This API call returns "status":"ok" if dns record does not exists
#We are creating a new txt record here, so we expect the "ok" status
if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
_err "Error"
_err "$response"
return 1
fi
_kinghost_rest POST "dns" "name=$fulldomain&content=$txtvalue"
if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
_err "Error"
_err "$response"
return 1
fi
return 0
}
# Usage: fulldomain txtvalue
# Used to remove the txt record after validation
dns_kinghost_rm() {
fulldomain=$1
txtvalue=$2
KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}"
KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}"
if [ -z "$KINGHOST_Password" ] || [ -z "$KINGHOST_Username" ]; then
KINGHOST_Password=""
KINGHOST_Username=""
_err "You don't specify KingHost api key and email yet."
_err "Please create you key and try again."
return 1
fi
_kinghost_rest DELETE "dns" "name=$fulldomain&content=$txtvalue"
if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
_err "Error"
_err "$response"
return 1
fi
return 0
}
#################### Private functions below ##################################
_kinghost_rest() {
method=$1
uri="$2"
data="$3"
_debug "$uri"
export _H1="X-Auth-Email: $KINGHOST_Username"
export _H2="X-Auth-Key: $KINGHOST_Password"
if [ "$method" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$KING_Api/$uri.json" "" "$method")"
else
response="$(_get "$KING_Api/$uri.json?$data")"
fi
if [ "$?" != "0" ]; then
_err "error $uri"
return 1
fi
_debug2 response "$response"
return 0
}

149
dnsapi/dns_leaseweb.sh

@ -0,0 +1,149 @@
#!/usr/bin/env sh
#Author: Rolph Haspers <r.haspers@global.leaseweb.com>
#Utilize leaseweb.com API to finish dns-01 verifications.
#Requires a Leaseweb API Key (export LSW_Key="Your Key")
#See http://developer.leaseweb.com for more information.
######## Public functions #####################
LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
#Usage: dns_leaseweb_add _acme-challenge.www.domain.com
dns_leaseweb_add() {
fulldomain=$1
txtvalue=$2
LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
if [ -z "$LSW_Key" ]; then
LSW_Key=""
_err "You don't specify Leaseweb 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 LSW_Key "$LSW_Key"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _root_domain "$_domain"
_debug _domain "$fulldomain"
if _lsw_api "POST" "$_domain" "$fulldomain" "$txtvalue"; then
if [ "$_code" = "201" ]; then
_info "Added, OK"
return 0
else
_err "Add txt record error, invalid code. Code: $_code"
return 1
fi
fi
_err "Add txt record error."
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_leaseweb_rm() {
fulldomain=$1
txtvalue=$2
LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _root_domain "$_domain"
_debug _domain "$fulldomain"
if _lsw_api "DELETE" "$_domain" "$fulldomain" "$txtvalue"; then
if [ "$_code" = "204" ]; then
_info "Deleted, OK"
return 0
else
_err "Delete txt record error."
return 1
fi
fi
_err "Delete txt record error."
return 1
}
#################### Private functions below ##################################
# _acme-challenge.www.domain.com
# returns
# _domain=domain.com
_get_root() {
rdomain=$1
i="$(echo "$rdomain" | tr '.' ' ' | wc -w)"
i=$(_math "$i" - 1)
while true; do
h=$(printf "%s" "$rdomain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
return 1 #not valid domain
fi
#Check API if domain exists
if _lsw_api "GET" "$h"; then
if [ "$_code" = "200" ]; then
_domain="$h"
return 0
fi
fi
i=$(_math "$i" - 1)
if [ "$i" -lt 2 ]; then
return 1 #not found, no need to check _acme-challenge.sub.domain in leaseweb api.
fi
done
return 1
}
_lsw_api() {
cmd=$1
d=$2
fd=$3
tvalue=$4
# Construct the HTTP Authorization header
export _H2="Content-Type: application/json"
export _H1="X-Lsw-Auth: ${LSW_Key}"
if [ "$cmd" = "GET" ]; then
response="$(_get "$LSW_API/$d")"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
_debug response "$response"
return 0
fi
if [ "$cmd" = "POST" ]; then
data="{\"name\": \"$fd.\",\"type\": \"TXT\",\"content\": [\"$tvalue\"],\"ttl\": 60}"
response="$(_post "$data" "$LSW_API/$d/resourceRecordSets" "$data" "POST")"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
_debug response "$response"
return 0
fi
if [ "$cmd" = "DELETE" ]; then
response="$(_post "" "$LSW_API/$d/resourceRecordSets/$fd/TXT" "" "DELETE")"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
_debug response "$response"
return 0
fi
return 1
}

67
dnsapi/dns_lexicon.sh

@ -5,22 +5,15 @@
# https://github.com/AnalogJ/lexicon # https://github.com/AnalogJ/lexicon
lexicon_cmd="lexicon" lexicon_cmd="lexicon"
wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
######## 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)
wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-dns-api"
_lexicon_init() {
if ! _exists "$lexicon_cmd"; then if ! _exists "$lexicon_cmd"; then
_err "Please install $lexicon_cmd first: $wiki" _err "Please install $lexicon_cmd first: $wiki"
return 1 return 1
fi fi
PROVIDER="${PROVIDER:-$(_readdomainconf PROVIDER)}"
if [ -z "$PROVIDER" ]; then if [ -z "$PROVIDER" ]; then
PROVIDER="" PROVIDER=""
_err "Please define env PROVIDER first: $wiki" _err "Please define env PROVIDER first: $wiki"
@ -33,46 +26,88 @@ dns_lexicon_add() {
# e.g. busybox-ash does not know [:upper:] # e.g. busybox-ash does not know [:upper:]
# shellcheck disable=SC2018,SC2019 # shellcheck disable=SC2018,SC2019
Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z') Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z')
eval "$Lx_name=\${$Lx_name:-$(_readaccountconf_mutable "$Lx_name")}"
Lx_name_v=$(eval echo \$"$Lx_name") Lx_name_v=$(eval echo \$"$Lx_name")
_secure_debug "$Lx_name" "$Lx_name_v" _secure_debug "$Lx_name" "$Lx_name_v"
if [ "$Lx_name_v" ]; then if [ "$Lx_name_v" ]; then
_saveaccountconf "$Lx_name" "$Lx_name_v"
_saveaccountconf_mutable "$Lx_name" "$Lx_name_v"
eval export "$Lx_name" eval export "$Lx_name"
fi fi
# shellcheck disable=SC2018,SC2019 # shellcheck disable=SC2018,SC2019
Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z') Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z')
eval "$Lx_token=\${$Lx_token:-$(_readaccountconf_mutable "$Lx_token")}"
Lx_token_v=$(eval echo \$"$Lx_token") Lx_token_v=$(eval echo \$"$Lx_token")
_secure_debug "$Lx_token" "$Lx_token_v" _secure_debug "$Lx_token" "$Lx_token_v"
if [ "$Lx_token_v" ]; then if [ "$Lx_token_v" ]; then
_saveaccountconf "$Lx_token" "$Lx_token_v"
_saveaccountconf_mutable "$Lx_token" "$Lx_token_v"
eval export "$Lx_token" eval export "$Lx_token"
fi fi
# shellcheck disable=SC2018,SC2019 # shellcheck disable=SC2018,SC2019
Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z') Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z')
eval "$Lx_password=\${$Lx_password:-$(_readaccountconf_mutable "$Lx_password")}"
Lx_password_v=$(eval echo \$"$Lx_password") Lx_password_v=$(eval echo \$"$Lx_password")
_secure_debug "$Lx_password" "$Lx_password_v" _secure_debug "$Lx_password" "$Lx_password_v"
if [ "$Lx_password_v" ]; then if [ "$Lx_password_v" ]; then
_saveaccountconf "$Lx_password" "$Lx_password_v"
_saveaccountconf_mutable "$Lx_password" "$Lx_password_v"
eval export "$Lx_password" eval export "$Lx_password"
fi fi
# shellcheck disable=SC2018,SC2019 # shellcheck disable=SC2018,SC2019
Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z') Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z')
eval "$Lx_domaintoken=\${$Lx_domaintoken:-$(_readaccountconf_mutable "$Lx_domaintoken")}"
Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken")
_secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v" _secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v"
if [ "$Lx_domaintoken_v" ]; then if [ "$Lx_domaintoken_v" ]; then
_saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v"
eval export "$Lx_domaintoken" eval export "$Lx_domaintoken"
_saveaccountconf "$Lx_domaintoken" "$Lx_domaintoken_v"
fi fi
$lexicon_cmd "$PROVIDER" create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
# shellcheck disable=SC2018,SC2019
Lx_api_key=$(echo LEXICON_"${PROVIDER}"_API_KEY | tr 'a-z' 'A-Z')
eval "$Lx_api_key=\${$Lx_api_key:-$(_readaccountconf_mutable "$Lx_api_key")}"
Lx_api_key_v=$(eval echo \$"$Lx_api_key")
_secure_debug "$Lx_api_key" "$Lx_api_key_v"
if [ "$Lx_api_key_v" ]; then
_saveaccountconf_mutable "$Lx_api_key" "$Lx_api_key_v"
eval export "$Lx_api_key"
fi
}
######## Public functions #####################
#Usage: dns_lexicon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_lexicon_add() {
fulldomain=$1
txtvalue=$2
if ! _lexicon_init; then
return 1
fi
domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
_secure_debug LEXICON_OPTS "$LEXICON_OPTS"
_savedomainconf LEXICON_OPTS "$LEXICON_OPTS"
# shellcheck disable=SC2086
$lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
} }
#fulldomain
#Usage: dns_lexicon_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_lexicon_rm() { dns_lexicon_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2
if ! _lexicon_init; then
return 1
fi
domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
# shellcheck disable=SC2086
$lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
} }

186
dnsapi/dns_linode_v4.sh

@ -0,0 +1,186 @@
#!/usr/bin/env sh
#Original Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
#v4 Update Author: Aaron W. Swenson <aaron@grandmasfridge.org>
LINODE_V4_API_URL="https://api.linode.com/v4/domains"
######## Public functions #####################
#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_linode_v4_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"
_payload="{
\"type\": \"TXT\",
\"name\": \"$_sub_domain\",
\"target\": \"$txtvalue\",
\"ttl_sec\": 300
}"
if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then
_resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\": *[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_v4_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"
if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then
response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
resource="$(echo "$response" | _egrep_o "\{.*\"name\": *\"$_sub_domain\".*}")"
if [ "$resource" ]; then
_resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_resource_id" ]; then
_debug _resource_id "$_resource_id"
if _rest DELETE "/$_domain_id/records/$_resource_id" && [ -n "$response" ]; then
# On 200/OK, empty set is returned. Check for error, if any.
_error_response=$(printf "%s\n" "$response" | _egrep_o "\"errors\"" | cut -d : -f 2 | tr -d " " | _head_n 1)
if [ -n "$_error_response" ]; then
_err "Error deleting the domain resource: $_error_response"
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_V4_API_KEY" ]; then
LINODE_V4_API_KEY=""
_err "You didn't specify the Linode v4 API key yet."
_err "Please create your key and try again."
return 1
fi
_saveaccountconf LINODE_V4_API_KEY "$LINODE_V4_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; 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\": *\"$h\".*}")"
if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\": *[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"
export _H3="Authorization: Bearer $LINODE_V4_API_KEY"
if [ "$mtd" != "GET" ]; then
# both POST and DELETE.
_debug data "$data"
response="$(_post "$data" "$LINODE_V4_API_URL$ep" "" "$mtd")"
else
response="$(_get "$LINODE_V4_API_URL$ep$data")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

302
dnsapi/dns_loopia.sh

@ -0,0 +1,302 @@
#!/usr/bin/env sh
#
#LOOPIA_User="username"
#
#LOOPIA_Password="password"
#
#LOOPIA_Api="https://api.loopia.<TLD>/RPCSERV"
LOOPIA_Api_Default="https://api.loopia.se/RPCSERV"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_loopia_add() {
fulldomain=$1
txtvalue=$2
if ! _loopia_load_config; then
return 1
fi
_loopia_save_config
_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"
_loopia_add_sub_domain "$_domain" "$_sub_domain"
_loopia_add_record "$_domain" "$_sub_domain" "$txtvalue"
}
dns_loopia_rm() {
fulldomain=$1
txtvalue=$2
if ! _loopia_load_config; then
return 1
fi
_loopia_save_config
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>removeSubdomain</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' "$LOOPIA_User" "$LOOPIA_Password" "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error could not get txt records"
return 1
fi
}
#################### Private functions below ##################################
_loopia_load_config() {
LOOPIA_Api="${LOOPIA_Api:-$(_readaccountconf_mutable LOOPIA_Api)}"
LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
if [ -z "$LOOPIA_Api" ]; then
LOOPIA_Api="$LOOPIA_Api_Default"
fi
if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
LOOPIA_User=""
LOOPIA_Password=""
_err "A valid Loopia API user and password not provided."
_err "Please provide a valid API user and try again."
return 1
fi
return 0
}
_loopia_save_config() {
if [ "$LOOPIA_Api" != "$LOOPIA_Api_Default" ]; then
_saveaccountconf_mutable LOOPIA_Api "$LOOPIA_Api"
fi
_saveaccountconf_mutable LOOPIA_User "$LOOPIA_User"
_saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
}
_loopia_get_records() {
domain=$1
sub_domain=$2
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>getZoneRecords</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "<array>"; then
_err "Error"
return 1
fi
return 0
}
_get_root() {
domain=$1
_debug "get root"
domain=$1
i=2
p=1
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>getDomains</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password)
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
while true; do
h=$(echo "$domain" | cut -d . -f $i-100)
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
}
_loopia_add_record() {
domain=$1
sub_domain=$2
txtval=$3
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>addZoneRecord</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<struct>
<member>
<name>type</name>
<value><string>TXT</string></value>
</member>
<member>
<name>priority</name>
<value><int>0</int></value>
</member>
<member>
<name>ttl</name>
<value><int>300</int></value>
</member>
<member>
<name>rdata</name>
<value><string>%s</string></value>
</member>
</struct>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error"
return 1
fi
return 0
}
_sub_domain_exists() {
domain=$1
sub_domain=$2
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>getSubdomains</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if _contains "$response" "$sub_domain"; then
return 0
fi
return 1
}
_loopia_add_sub_domain() {
domain=$1
sub_domain=$2
if _sub_domain_exists "$domain" "$sub_domain"; then
return 0
fi
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>addSubdomain</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error"
return 1
fi
return 0
}

40
dnsapi/dns_lua.sh

@ -8,7 +8,6 @@
#LUA_Email="user@luadns.net" #LUA_Email="user@luadns.net"
LUA_Api="https://api.luadns.com/v1" LUA_Api="https://api.luadns.com/v1"
LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64)
######## Public functions ##################### ######## Public functions #####################
@ -17,6 +16,10 @@ dns_lua_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}"
LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}"
LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64)
if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then
LUA_Key="" LUA_Key=""
LUA_Email="" LUA_Email=""
@ -26,8 +29,8 @@ dns_lua_add() {
fi fi
#save the api key and email to the account conf file. #save the api key and email to the account conf file.
_saveaccountconf LUA_Key "$LUA_Key"
_saveaccountconf LUA_Email "$LUA_Email"
_saveaccountconf_mutable LUA_Key "$LUA_Key"
_saveaccountconf_mutable LUA_Email "$LUA_Email"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -38,17 +41,6 @@ dns_lua_add() {
_debug _sub_domain "$_sub_domain" _debug _sub_domain "$_sub_domain"
_debug _domain "$_domain" _debug _domain "$_domain"
_debug "Getting txt records"
_LUA_rest GET "zones/${_domain_id}/records"
if ! _contains "$response" "\"id\":"; then
_err "Error"
return 1
fi
count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ")
_debug count "$count"
if [ "$count" = "0" ]; then
_info "Adding record" _info "Adding record"
if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
if _contains "$response" "$fulldomain"; then if _contains "$response" "$fulldomain"; then
@ -60,28 +52,16 @@ dns_lua_add() {
return 1 return 1
fi fi
fi fi
_err "Add txt record error."
else
_info "Updating record"
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1)
_debug "record_id" "$record_id"
_LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":$record_id,\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":$_domain_id,\"ttl\":120}"
if [ "$?" = "0" ] && _contains "$response" "updated_at"; then
_info "Updated!"
#todo: check if the record takes effect
return 0
fi
_err "Update error"
return 1
fi
} }
#fulldomain #fulldomain
dns_lua_rm() { dns_lua_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}"
LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}"
LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64)
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
_err "invalid domain" _err "invalid domain"

69
dnsapi/dns_maradns.sh

@ -0,0 +1,69 @@
#!/usr/bin/env sh
#Usage: dns_maradns_add _acme-challenge.www.domain.com "token"
dns_maradns_add() {
fulldomain="$1"
txtvalue="$2"
MARA_ZONE_FILE="${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}"
MARA_DUENDE_PID_PATH="${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}"
_check_zone_file "$MARA_ZONE_FILE" || return 1
_check_duende_pid_path "$MARA_DUENDE_PID_PATH" || return 1
_saveaccountconf_mutable MARA_ZONE_FILE "$MARA_ZONE_FILE"
_saveaccountconf_mutable MARA_DUENDE_PID_PATH "$MARA_DUENDE_PID_PATH"
printf "%s. TXT '%s' ~\n" "$fulldomain" "$txtvalue" >>"$MARA_ZONE_FILE"
_reload_maradns "$MARA_DUENDE_PID_PATH" || return 1
}
#Usage: dns_maradns_rm _acme-challenge.www.domain.com "token"
dns_maradns_rm() {
fulldomain="$1"
txtvalue="$2"
MARA_ZONE_FILE="${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}"
MARA_DUENDE_PID_PATH="${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}"
_check_zone_file "$MARA_ZONE_FILE" || return 1
_check_duende_pid_path "$MARA_DUENDE_PID_PATH" || return 1
_saveaccountconf_mutable MARA_ZONE_FILE "$MARA_ZONE_FILE"
_saveaccountconf_mutable MARA_DUENDE_PID_PATH "$MARA_DUENDE_PID_PATH"
_sed_i "/^$fulldomain.\+TXT '$txtvalue' ~/d" "$MARA_ZONE_FILE"
_reload_maradns "$MARA_DUENDE_PID_PATH" || return 1
}
_check_zone_file() {
zonefile="$1"
if [ -z "$zonefile" ]; then
_err "MARA_ZONE_FILE not passed!"
return 1
elif [ ! -w "$zonefile" ]; then
_err "MARA_ZONE_FILE not writable: $zonefile"
return 1
fi
}
_check_duende_pid_path() {
pidpath="$1"
if [ -z "$pidpath" ]; then
_err "MARA_DUENDE_PID_PATH not passed!"
return 1
fi
if [ ! -r "$pidpath" ]; then
_err "MARA_DUENDE_PID_PATH not readable: $pidpath"
return 1
fi
}
_reload_maradns() {
pidpath="$1"
kill -s HUP -- "$(cat "$pidpath")"
if [ $? -ne 0 ]; then
_err "Unable to reload MaraDNS, kill returned $?"
return 1
fi
}

24
dnsapi/dns_me.sh

@ -43,9 +43,6 @@ dns_me_add() {
return 1 return 1
fi fi
count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | cut -d : -f 2)
_debug count "$count"
if [ "$count" = "0" ]; then
_info "Adding record" _info "Adding record"
if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then
if printf -- "%s" "$response" | grep \"id\": >/dev/null; then if printf -- "%s" "$response" | grep \"id\": >/dev/null; then
@ -57,21 +54,6 @@ dns_me_add() {
return 1 return 1
fi fi
fi fi
_err "Add txt record error."
else
_info "Updating record"
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1)
_debug "record_id" "$record_id"
_me_rest PUT "$_domain_id/records/$record_id/" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"
if [ "$?" = "0" ]; then
_info "Updated"
#todo: check if the record takes effect
return 0
fi
_err "Update error"
return 1
fi
} }
@ -96,7 +78,7 @@ dns_me_rm() {
if [ "$count" = "0" ]; then if [ "$count" = "0" ]; then
_info "Don't need to remove." _info "Don't need to remove."
else else
record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1)
record_id=$(printf "%s\n" "$response" | _egrep_o ",\"value\":\"..$txtvalue..\",\"id\":[^,]*" | cut -d : -f 3 | head -n 1)
_debug "record_id" "$record_id" _debug "record_id" "$record_id"
if [ -z "$record_id" ]; then if [ -z "$record_id" ]; then
_err "Can not get record id to remove." _err "Can not get record id to remove."
@ -132,7 +114,7 @@ _get_root() {
fi fi
if _contains "$response" "\"name\":\"$h\""; then if _contains "$response" "\"name\":\"$h\""; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}')
_domain_id=$(printf "%s\n" "$response" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*"id":([0-9]+).*$/\1/')
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h" _domain="$h"
@ -152,7 +134,7 @@ _me_rest() {
data="$3" data="$3"
_debug "$ep" _debug "$ep"
cdate=$(date -u +"%a, %d %b %Y %T %Z")
cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z")
hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex)
export _H1="x-dnsme-apiKey: $ME_Key" export _H1="x-dnsme-apiKey: $ME_Key"

210
dnsapi/dns_miab.sh

@ -0,0 +1,210 @@
#!/usr/bin/env sh
# Name: dns_miab.sh
#
# Authors:
# Darven Dissek 2018
# William Gertz 2019
#
# Thanks to Neil Pang and other developers here for code reused from acme.sh from DNS-01
# used to communicate with the MailinaBox Custom DNS API
# Report Bugs here:
# https://github.com/billgertz/MIAB_dns_api (for dns_miab.sh)
# https://github.com/acmesh-official/acme.sh (for acme.sh)
#
######## Public functions #####################
#Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_miab_add() {
fulldomain=$1
txtvalue=$2
_info "Using miab challange add"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
#retrieve MIAB environemt vars
if ! _retrieve_miab_env; then
return 1
fi
#check domain and seperate into doamin and host
if ! _get_root "$fulldomain"; then
_err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
return 1
fi
_debug2 _sub_domain "$_sub_domain"
_debug2 _domain "$_domain"
#add the challenge record
_api_path="custom/${fulldomain}/txt"
_miab_rest "$txtvalue" "$_api_path" "POST"
#check if result was good
if _contains "$response" "updated DNS"; then
_info "Successfully created the txt record"
return 0
else
_err "Error encountered during record add"
_err "$response"
return 1
fi
}
#Usage: dns_miab_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_miab_rm() {
fulldomain=$1
txtvalue=$2
_info "Using miab challage delete"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
#retrieve MIAB environemt vars
if ! _retrieve_miab_env; then
return 1
fi
#check domain and seperate into doamin and host
if ! _get_root "$fulldomain"; then
_err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
return 1
fi
_debug2 _sub_domain "$_sub_domain"
_debug2 _domain "$_domain"
#Remove the challenge record
_api_path="custom/${fulldomain}/txt"
_miab_rest "$txtvalue" "$_api_path" "DELETE"
#check if result was good
if _contains "$response" "updated DNS"; then
_info "Successfully removed the txt record"
return 0
else
_err "Error encountered during record remove"
_err "$response"
return 1
fi
}
#################### Private functions below ##################################
#
#Usage: _get_root _acme-challenge.www.domain.com
#Returns:
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
_passed_domain=$1
_debug _passed_domain "$_passed_domain"
_i=2
_p=1
#get the zones hosed on MIAB server, must be a json stream
_miab_rest "" "zones" "GET"
if ! _is_json "$response"; then
_err "ERROR fetching domain list"
_err "$response"
return 1
fi
#cycle through the passed domain seperating out a test domain discarding
# the subdomain by marching thorugh the dots
while true; do
_test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f ${_i}-100)
_debug _test_domain "$_test_domain"
if [ -z "$_test_domain" ]; then
return 1
fi
#report found if the test domain is in the json response and
# report the subdomain
if _contains "$response" "\"$_test_domain\""; then
_sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-${_p})
_domain=${_test_domain}
return 0
fi
#cycle to the next dot in the passed domain
_p=${_i}
_i=$(_math "$_i" + 1)
done
return 1
}
#Usage: _retrieve_miab_env
#Returns (from store or environment variables):
# MIAB_Username
# MIAB_Password
# MIAB_Server
#retrieve MIAB environment variables, report errors and quit if problems
_retrieve_miab_env() {
MIAB_Username="${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}"
MIAB_Password="${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}"
MIAB_Server="${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}"
#debug log the environmental variables
_debug MIAB_Username "$MIAB_Username"
_debug MIAB_Password "$MIAB_Password"
_debug MIAB_Server "$MIAB_Server"
#check if MIAB environemt vars set and quit if not
if [ -z "$MIAB_Username" ] || [ -z "$MIAB_Password" ] || [ -z "$MIAB_Server" ]; then
_err "You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server."
_err "Please check these environment variables and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable MIAB_Username "$MIAB_Username"
_saveaccountconf_mutable MIAB_Password "$MIAB_Password"
_saveaccountconf_mutable MIAB_Server "$MIAB_Server"
}
#Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST"
#Returns: "updated DNS: domain.com"
#rest interface MIAB dns
_miab_rest() {
_data="$1"
_api_path="$2"
_httpmethod="$3"
#encode username and password for basic authentication
_credentials="$(printf "%s" "$MIAB_Username:$MIAB_Password" | _base64)"
export _H1="Authorization: Basic $_credentials"
_url="https://${MIAB_Server}/admin/dns/${_api_path}"
_debug2 _data "$_data"
_debug _api_path "$_api_path"
_debug2 _url "$_url"
_debug2 _credentails "$_credentials"
_debug _httpmethod "$_httpmethod"
if [ "$_httpmethod" = "GET" ]; then
response="$(_get "$_url")"
else
response="$(_post "$_data" "$_url" "" "$_httpmethod")"
fi
_retcode="$?"
if [ "$_retcode" != "0" ]; then
_err "MIAB REST authentication failed on $_httpmethod"
return 1
fi
_debug response "$response"
return 0
}
#Usage: _is_json "\[\n "mydomain.com"\n]"
#Reurns "\[\n "mydomain.com"\n]"
#returns the string if it begins and ends with square braces
_is_json() {
_str="$(echo "$1" | _normalizeJson)"
echo "$_str" | grep '^\[.*\]$' >/dev/null 2>&1
}

159
dnsapi/dns_misaka.sh

@ -0,0 +1,159 @@
#!/usr/bin/env sh
# bug reports to support+acmesh@misaka.io
# based on dns_nsone.sh by dev@1e.ca
#
#Misaka_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
Misaka_Api="https://dnsapi.misaka.io/dns"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_misaka_add() {
fulldomain=$1
txtvalue=$2
if [ -z "$Misaka_Key" ]; then
Misaka_Key=""
_err "You didn't specify misaka.io 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 Misaka_Key "$Misaka_Key"
_debug "checking root zone [$fulldomain]"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug "Getting txt records"
_misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
if ! _contains "$response" "\"results\":"; then
_err "Error"
return 1
fi
count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
_debug count "$count"
if [ "$count" = "0" ]; then
_info "Adding record"
if _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then
_debug response "$response"
if _contains "$response" "$_sub_domain"; then
_info "Added"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
else
_info "Updating record"
_misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}"
if [ "$?" = "0" ] && _contains "$response" "$_sub_domain"; then
_info "Updated!"
#todo: check if the record takes effect
return 0
fi
_err "Update error"
return 1
fi
}
#fulldomain
dns_misaka_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"
_misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
_debug count "$count"
if [ "$count" = "0" ]; then
_info "Don't need to remove."
else
if ! _misaka_rest DELETE "zones/${_domain}/recordsets/${_sub_domain}/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 ! _misaka_rest GET "zones?limit=1000"; 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
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_misaka_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Content-Type: application/json"
export _H2="User-Agent: acme.sh/$VER misaka-dns-acmesh/20191213"
export _H3="Authorization: Token $Misaka_Key"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$Misaka_Api/$ep" "" "$m")"
else
response="$(_get "$Misaka_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

4
dnsapi/dns_myapi.sh

@ -7,10 +7,12 @@
#returns 0 means success, otherwise error. #returns 0 means success, otherwise error.
# #
#Author: Neilpang #Author: Neilpang
#Report Bugs here: https://github.com/Neilpang/acme.sh
#Report Bugs here: https://github.com/acmesh-official/acme.sh
# #
######## Public functions ##################### ######## Public functions #####################
# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_myapi_add() { dns_myapi_add() {
fulldomain=$1 fulldomain=$1

97
dnsapi/dns_mydevil.sh

@ -0,0 +1,97 @@
#!/usr/bin/env sh
# MyDevil.net API (2019-02-03)
#
# MyDevil.net already supports automatic Let's Encrypt certificates,
# except for wildcard domains.
#
# This script depends on `devil` command that MyDevil.net provides,
# which means that it works only on server side.
#
# Author: Marcin Konicki <https://ahwayakchih.neoni.net>
#
######## Public functions #####################
#Usage: dns_mydevil_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_mydevil_add() {
fulldomain=$1
txtvalue=$2
domain=""
if ! _exists "devil"; then
_err "Could not find 'devil' command."
return 1
fi
_info "Using mydevil"
domain=$(mydevil_get_domain "$fulldomain")
if [ -z "$domain" ]; then
_err "Invalid domain name: could not find root domain of $fulldomain."
return 1
fi
# No need to check if record name exists, `devil` always adds new record.
# In worst case scenario, we end up with multiple identical records.
_info "Adding $fulldomain record for domain $domain"
if devil dns add "$domain" "$fulldomain" TXT "$txtvalue"; then
_info "Successfully added TXT record, ready for validation."
return 0
else
_err "Unable to add DNS record."
return 1
fi
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_mydevil_rm() {
fulldomain=$1
txtvalue=$2
domain=""
if ! _exists "devil"; then
_err "Could not find 'devil' command."
return 1
fi
_info "Using mydevil"
domain=$(mydevil_get_domain "$fulldomain")
if [ -z "$domain" ]; then
_err "Invalid domain name: could not find root domain of $fulldomain."
return 1
fi
# catch one or more numbers
num='[0-9][0-9]*'
# catch one or more whitespace
w=$(printf '[\t ][\t ]*')
# catch anything, except newline
any='.*'
# filter to make sure we do not delete other records
validRecords="^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$"
for id in $(devil dns list "$domain" | tail -n+2 | grep "${validRecords}" | cut -w -s -f 1); do
_info "Removing record $id from domain $domain"
devil dns del "$domain" "$id" || _err "Could not remove DNS record."
done
}
#################### Private functions below ##################################
# Usage: domain=$(mydevil_get_domain "_acme-challenge.www.domain.com" || _err "Invalid domain name")
# echo $domain
mydevil_get_domain() {
fulldomain=$1
domain=""
for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do
if _endswith "$fulldomain" "$domain"; then
printf -- "%s" "$domain"
return 0
fi
done
return 1
}

210
dnsapi/dns_mydnsjp.sh

@ -0,0 +1,210 @@
#!/usr/bin/env sh
#Here is a api script for MyDNS.JP.
#This file name is "dns_mydnsjp.sh"
#So, here must be a method dns_mydnsjp_add()
#Which will be called by acme.sh to add the txt record to your api system.
#returns 0 means success, otherwise error.
#
#Author: epgdatacapbon
#Report Bugs here: https://github.com/epgdatacapbon/acme.sh
#
######## Public functions #####################
# Export MyDNS.JP MasterID and Password in following variables...
# MYDNSJP_MasterID=MasterID
# MYDNSJP_Password=Password
MYDNSJP_API="https://www.mydns.jp"
#Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_mydnsjp_add() {
fulldomain=$1
txtvalue=$2
_info "Using mydnsjp"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
# Load the credentials from the account conf file
MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}"
MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}"
if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then
MYDNSJP_MasterID=""
MYDNSJP_Password=""
_err "You don't specify mydnsjp api MasterID and Password yet."
_err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again."
return 1
fi
# Save the credentials to the account conf file
_saveaccountconf_mutable MYDNSJP_MasterID "$MYDNSJP_MasterID"
_saveaccountconf_mutable MYDNSJP_Password "$MYDNSJP_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 _mydnsjp_api "REGIST" "$_domain" "$txtvalue"; 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_mydnsjp_rm() {
fulldomain=$1
txtvalue=$2
_info "Removing TXT record"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
# Load the credentials from the account conf file
MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}"
MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}"
if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then
MYDNSJP_MasterID=""
MYDNSJP_Password=""
_err "You don't specify mydnsjp api MasterID and Password yet."
_err "Please export as MYDNSJP_MasterID / MYDNSJP_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 _mydnsjp_api "DELETE" "$_domain" "$txtvalue"; then
if printf -- "%s" "$response" | grep "OK." >/dev/null; then
_info "Deleted, OK"
return 0
else
_err "Delete txt record error."
return 1
fi
fi
_err "Delete txt record error."
return 1
}
#################### 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
# Get the root domain
_mydnsjp_retrieve_domain
if [ "$?" != "0" ]; then
# not valid
return 1
fi
while true; do
_domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
if [ -z "$_domain" ]; then
# not valid
return 1
fi
if [ "$_domain" = "$_root_domain" ]; then
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p)
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
# Retrieve the root domain
# returns 0 success
_mydnsjp_retrieve_domain() {
_debug "Login to MyDNS.JP"
response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")"
cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
# If cookies is not empty then logon successful
if [ -z "$cookie" ]; then
_err "Fail to get a cookie."
return 1
fi
_debug "Retrieve DOMAIN INFO page"
export _H1="Cookie:${cookie}"
response="$(_get "$MYDNSJP_API/?MENU=300")"
if [ "$?" != "0" ]; then
_err "Fail to retrieve DOMAIN INFO."
return 1
fi
_root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/')
# Logout
response="$(_get "$MYDNSJP_API/?MENU=090")"
_debug _root_domain "$_root_domain"
if [ -z "$_root_domain" ]; then
_err "Fail to get the root domain."
return 1
fi
return 0
}
_mydnsjp_api() {
cmd=$1
domain=$2
txtvalue=$3
# Base64 encode the credentials
credentials=$(printf "%s:%s" "$MYDNSJP_MasterID" "$MYDNSJP_Password" | _base64)
# Construct the HTTP Authorization header
export _H1="Content-Type: application/x-www-form-urlencoded"
export _H2="Authorization: Basic ${credentials}"
response="$(_post "CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd" "$MYDNSJP_API/directedit.html")"
if [ "$?" != "0" ]; then
_err "error $domain"
return 1
fi
_debug2 response "$response"
return 0
}

407
dnsapi/dns_namecheap.sh

@ -0,0 +1,407 @@
#!/usr/bin/env sh
# Namecheap API
# https://www.namecheap.com/support/api/intro.aspx
#
# Requires Namecheap API key set in
#NAMECHEAP_API_KEY,
#NAMECHEAP_USERNAME,
#NAMECHEAP_SOURCEIP
# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
######## Public functions #####################
NAMECHEAP_API="https://api.namecheap.com/xml.response"
#Usage: dns_namecheap_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_namecheap_add() {
fulldomain=$1
txtvalue=$2
if ! _namecheap_check_config; then
_err "$error"
return 1
fi
if ! _namecheap_set_publicip; then
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug domain "$_domain"
_debug sub_domain "$_sub_domain"
_set_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_namecheap_rm() {
fulldomain=$1
txtvalue=$2
if ! _namecheap_set_publicip; then
return 1
fi
if ! _namecheap_check_config; then
_err "$error"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug domain "$_domain"
_debug sub_domain "$_sub_domain"
_del_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
fulldomain=$1
if ! _get_root_by_getList "$fulldomain"; then
_debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api."
# The above "getList" api will only return hosts *owned* by the calling user. However, if the calling
# user is not the owner, but still has administrative rights, we must query the getHosts api directly.
# See this comment and the official namecheap response: http://disq.us/p/1q6v9x9
if ! _get_root_by_getHosts "$fulldomain"; then
return 1
fi
fi
return 0
}
_get_root_by_getList() {
domain=$1
if ! _namecheap_post "namecheap.domains.getList"; then
_err "$error"
return 1
fi
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
#not valid
return 1
fi
if ! _contains "$h" "\\."; then
#not valid
return 1
fi
if ! _contains "$response" "$h"; then
_debug "$h not found"
else
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
_get_root_by_getHosts() {
i=100
p=99
while [ $p -ne 0 ]; do
h=$(printf "%s" "$1" | cut -d . -f $i-100)
if [ -n "$h" ]; then
if _contains "$h" "\\."; then
_debug h "$h"
if _namecheap_set_tld_sld "$h"; then
_sub_domain=$(printf "%s" "$1" | cut -d . -f 1-$p)
_domain="$h"
return 0
else
_debug "$h not found"
fi
fi
fi
i="$p"
p=$(_math "$p" - 1)
done
return 1
}
_namecheap_set_publicip() {
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_debug sourceip "$NAMECHEAP_SOURCEIP"
ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https):\/\/.*')
_debug2 ip "$ip"
_debug2 addr "$addr"
if [ -n "$ip" ]; then
_publicip="$ip"
elif [ -n "$addr" ]; then
_publicip=$(_get "$addr")
else
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
fi
fi
_debug publicip "$_publicip"
return 0
}
_namecheap_post() {
command=$1
data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}"
_debug2 "_namecheap_post data" "$data"
response="$(_post "$data" "$NAMECHEAP_API" "" "POST")"
_debug2 response "$response"
if _contains "$response" "Status=\"ERROR\"" >/dev/null; then
error=$(echo "$response" | _egrep_o ">.*<\\/Error>" | cut -d '<' -f 1 | tr -d '>')
_err "error $error"
return 1
fi
return 0
}
_namecheap_parse_host() {
_host=$1
_debug _host "$_host"
_hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2)
_hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2)
_hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2)
_hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2)
_hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2)
_hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2)
_debug hostid "$_hostid"
_debug hostname "$_hostname"
_debug hosttype "$_hosttype"
_debug hostaddress "$_hostaddress"
_debug hostmxpref "$_hostmxpref"
_debug hostttl "$_hostttl"
}
_namecheap_check_config() {
if [ -z "$NAMECHEAP_API_KEY" ]; then
_err "No API key specified for Namecheap API."
_err "Create your key and export it as NAMECHEAP_API_KEY"
return 1
fi
if [ -z "$NAMECHEAP_USERNAME" ]; then
_err "No username key specified for Namecheap API."
_err "Create your key and export it as NAMECHEAP_USERNAME"
return 1
fi
_saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
return 0
}
_set_namecheap_TXT() {
subdomain=$2
txt=$3
if ! _namecheap_set_tld_sld "$1"; then
return 1
fi
request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
hosts=$(echo "$response" | _egrep_o '<host[^>]*')
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
return 1
fi
_namecheap_reset_hostList
while read -r host; do
if _contains "$host" "<host"; then
_namecheap_parse_host "$host"
_debug2 _hostname "_hostname"
_debug2 _hosttype "_hosttype"
_debug2 _hostaddress "_hostaddress"
_debug2 _hostmxpref "_hostmxpref"
_hostaddress="$(printf "%s" "$_hostaddress" | _url_encode)"
_debug2 "encoded _hostaddress" "_hostaddress"
_namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
fi
done <<EOT
echo "$hosts"
EOT
_namecheap_add_host "$subdomain" "TXT" "$txt" 10 120
_debug hostrequestfinal "$_hostrequest"
request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
return 0
}
_del_namecheap_TXT() {
subdomain=$2
txt=$3
if ! _namecheap_set_tld_sld "$1"; then
return 1
fi
request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
hosts=$(echo "$response" | _egrep_o '<host[^>]*')
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
return 1
fi
_namecheap_reset_hostList
found=0
while read -r host; do
if _contains "$host" "<host"; then
_namecheap_parse_host "$host"
if [ "$_hosttype" = "TXT" ] && [ "$_hostname" = "$subdomain" ] && [ "$_hostaddress" = "$txt" ]; then
_debug "TXT entry found"
found=1
else
_hostaddress="$(printf "%s" "$_hostaddress" | _url_encode)"
_namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
fi
fi
done <<EOT
echo "$hosts"
EOT
if [ $found -eq 0 ]; then
_debug "TXT entry not found"
return 0
fi
_debug hostrequestfinal "$_hostrequest"
request="namecheap.domains.dns.setHosts&SLD=${_sld}&TLD=${_tld}${_hostrequest}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
return 0
}
_namecheap_reset_hostList() {
_hostindex=0
_hostrequest=""
}
#Usage: _namecheap_add_host HostName RecordType Address MxPref TTL
_namecheap_add_host() {
_hostindex=$(_math "$_hostindex" + 1)
_hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' "$_hostrequest" "$_hostindex" "$1" "$_hostindex" "$2" "$_hostindex" "$3" "$_hostindex" "$4" "$_hostindex" "$5")
}
_namecheap_set_tld_sld() {
domain=$1
_tld=""
_sld=""
i=2
while true; do
_tld=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug tld "$_tld"
if [ -z "$_tld" ]; then
_debug "invalid tld"
return 1
fi
j=$(_math "$i" - 1)
_sld=$(printf "%s" "$domain" | cut -d . -f 1-"$j")
_debug sld "$_sld"
if [ -z "$_sld" ]; then
_debug "invalid sld"
return 1
fi
request="namecheap.domains.dns.getHosts&SLD=$_sld&TLD=$_tld"
if ! _namecheap_post "$request"; then
_debug "sld($_sld)/tld($_tld) not found"
else
_debug "sld($_sld)/tld($_tld) found"
return 0
fi
i=$(_math "$i" + 1)
done
}

86
dnsapi/dns_namecom.sh

@ -1,17 +1,20 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#Author: RaidneII
#Author: RaidenII
#Created 06/28/2017 #Created 06/28/2017
#Updated 03/01/2018, rewrote to support name.com API v4
#Utilize name.com API to finish dns-01 verifications. #Utilize name.com API to finish dns-01 verifications.
######## Public functions ##################### ######## Public functions #####################
Namecom_API="https://api.name.com/api"
Namecom_API="https://api.name.com/v4"
#Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_namecom_add() { dns_namecom_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}"
Namecom_Token="${Namecom_Token:-$(_readaccountconf_mutable Namecom_Token)}"
# First we need name.com credentials. # First we need name.com credentials.
if [ -z "$Namecom_Username" ]; then if [ -z "$Namecom_Username" ]; then
Namecom_Username="" Namecom_Username=""
@ -26,10 +29,11 @@ dns_namecom_add() {
_err "Please specify that in your environment variable." _err "Please specify that in your environment variable."
return 1 return 1
fi fi
_debug Namecom_Username "$Namecom_Username"
_secure_debug Namecom_Token "$Namecom_Token"
# Save them in configuration. # Save them in configuration.
_saveaccountconf Namecom_Username "$Namecom_Username"
_saveaccountconf Namecom_Token "$Namecom_Token"
_saveaccountconf_mutable Namecom_Username "$Namecom_Username"
_saveaccountconf_mutable Namecom_Token "$Namecom_Token"
# Login in using API # Login in using API
if ! _namecom_login; then if ! _namecom_login; then
@ -39,21 +43,18 @@ dns_namecom_add() {
# Find domain in domain list. # Find domain in domain list.
if ! _namecom_get_root "$fulldomain"; then if ! _namecom_get_root "$fulldomain"; then
_err "Unable to find domain specified." _err "Unable to find domain specified."
_namecom_logout
return 1 return 1
fi fi
# Add TXT record. # Add TXT record.
_namecom_addtxt_json="{\"hostname\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":\"300\",\"priority\":\"10\"}"
if _namecom_rest POST "dns/create/$_domain" "$_namecom_addtxt_json"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
if [ "$retcode" ]; then
_namecom_addtxt_json="{\"host\":\"$_sub_domain\",\"type\":\"TXT\",\"answer\":\"$txtvalue\",\"ttl\":\"300\"}"
if _namecom_rest POST "domains/$_domain/records" "$_namecom_addtxt_json"; then
_retvalue=$(echo "$response" | _egrep_o "\"$_sub_domain\"")
if [ "$_retvalue" ]; then
_info "Successfully added TXT record, ready for validation." _info "Successfully added TXT record, ready for validation."
_namecom_logout
return 0 return 0
else else
_err "Unable to add the DNS record." _err "Unable to add the DNS record."
_namecom_logout
return 1 return 1
fi fi
fi fi
@ -65,6 +66,8 @@ dns_namecom_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}"
Namecom_Token="${Namecom_Token:-$(_readaccountconf_mutable Namecom_Token)}"
if ! _namecom_login; then if ! _namecom_login; then
return 1 return 1
fi fi
@ -72,38 +75,29 @@ dns_namecom_rm() {
# Find domain in domain list. # Find domain in domain list.
if ! _namecom_get_root "$fulldomain"; then if ! _namecom_get_root "$fulldomain"; then
_err "Unable to find domain specified." _err "Unable to find domain specified."
_namecom_logout
return 1 return 1
fi fi
# Get the record id. # Get the record id.
if _namecom_rest GET "dns/list/$_domain"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
if [ "$retcode" ]; then
_record_id=$(printf "%s\n" "$response" | _egrep_o "\"record_id\":\"[0-9]+\",\"name\":\"$fulldomain\",\"type\":\"TXT\"" | cut -d \" -f 4)
if _namecom_rest GET "domains/$_domain/records"; then
_record_id=$(echo "$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" _debug record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge." _info "Successfully retrieved the record id for ACME challenge."
else else
_err "Unable to retrieve the record id." _err "Unable to retrieve the record id."
_namecom_logout
return 1 return 1
fi fi
fi fi
# Remove the DNS record using record id. # Remove the DNS record using record id.
_namecom_rmtxt_json="{\"record_id\":\"$_record_id\"}"
if _namecom_rest POST "dns/delete/$_domain" "$_namecom_rmtxt_json"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
if [ "$retcode" ]; then
if _namecom_rest DELETE "domains/$_domain/records/$_record_id"; then
_info "Successfully removed the TXT record." _info "Successfully removed the TXT record."
_namecom_logout
return 0 return 0
else else
_err "Unable to remove the DNS record."
_namecom_logout
_err "Unable to delete record id."
return 1 return 1
fi fi
fi
} }
#################### Private functions below ################################## #################### Private functions below ##################################
@ -112,8 +106,9 @@ _namecom_rest() {
param=$2 param=$2
data=$3 data=$3
export _H1="Content-Type: application/json"
export _H2="Api-Session-Token: $sessionkey"
export _H1="Authorization: Basic $_namecom_auth"
export _H2="Content-Type: application/json"
if [ "$method" != "GET" ]; then if [ "$method" != "GET" ]; then
response="$(_post "$data" "$Namecom_API/$param" "" "$method")" response="$(_post "$data" "$Namecom_API/$param" "" "$method")"
else else
@ -130,45 +125,30 @@ _namecom_rest() {
} }
_namecom_login() { _namecom_login() {
namecom_login_json="{\"username\":\"$Namecom_Username\",\"api_token\":\"$Namecom_Token\"}"
# 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 POST "login" "$namecom_login_json"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
if _namecom_rest GET "hello"; then
retcode=$(echo "$response" | _egrep_o "\"username\"\:\"$Namecom_Username\"")
if [ "$retcode" ]; then if [ "$retcode" ]; then
_info "Successfully logged in. Fetching session token..."
sessionkey=$(printf "%s\n" "$response" | _egrep_o "\"session_token\":\".+" | cut -d \" -f 4)
if [ ! -z "$sessionkey" ]; then
_debug sessionkey "$sessionkey"
_info "Session key obtained."
else
_err "Unable to get session key."
return 1
fi
_info "Successfully logged in."
else else
_err "$response"
_err "Please add your ip to api whitelist"
_err "Logging in failed." _err "Logging in failed."
return 1 return 1
fi fi
fi fi
} }
_namecom_logout() {
if _namecom_rest GET "logout"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100")
if [ "$retcode" ]; then
_info "Successfully logged out."
else
_err "Error logging out."
return 1
fi
fi
}
_namecom_get_root() { _namecom_get_root() {
domain=$1 domain=$1
i=2 i=2
p=1 p=1
if ! _namecom_rest GET "domain/list"; then
if ! _namecom_rest GET "domains"; then
return 1 return 1
fi fi

11
dnsapi/dns_namesilo.sh

@ -59,9 +59,14 @@ dns_namesilo_rm() {
if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then
retcode=$(printf "%s\n" "$response" | _egrep_o "<code>300") retcode=$(printf "%s\n" "$response" | _egrep_o "<code>300")
if [ "$retcode" ]; then 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"
_record_id=$(echo "$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"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge." _info "Successfully retrieved the record id for ACME challenge."
else
_info "Empty record id, it seems no such record."
return 0
fi
else else
_err "Unable to retrieve the record id." _err "Unable to retrieve the record id."
return 1 return 1
@ -105,7 +110,7 @@ _get_root() {
return 1 return 1
fi fi
if _contains "$response" "$host"; then
if _contains "$response" "<domain>$host"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$host" _domain="$host"
return 0 return 0

131
dnsapi/dns_nederhost.sh

@ -0,0 +1,131 @@
#!/usr/bin/env sh
#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghjk"
NederHost_Api="https://api.nederhost.nl/dns/v1"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_nederhost_add() {
fulldomain=$1
txtvalue=$2
NederHost_Key="${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}"
if [ -z "$NederHost_Key" ]; then
NederHost_Key=""
_err "You didn't specify a NederHost api key."
_err "You can get yours from https://www.nederhost.nl/mijn_nederhost"
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable NederHost_Key "$NederHost_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"
_info "Adding record"
if _nederhost_rest PATCH "zones/$_domain/records/$fulldomain/TXT" "[{\"content\":\"$txtvalue\",\"ttl\":60}]"; then
if _contains "$response" "$fulldomain"; then
_info "Added, OK"
return 0
else
_err "Add txt record error."
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain txtvalue
dns_nederhost_rm() {
fulldomain=$1
txtvalue=$2
NederHost_Key="${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}"
if [ -z "$NederHost_Key" ]; then
NederHost_Key=""
_err "You didn't specify a NederHost api key."
_err "You can get yours from https://www.nederhost.nl/mijn_nederhost"
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 "Removing txt record"
_nederhost_rest DELETE "zones/${_domain}/records/$fulldomain/TXT?content=$txtvalue"
}
#################### 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
_domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_debug _domain "$_domain"
if [ -z "$_domain" ]; then
#not valid
return 1
fi
if _nederhost_rest GET "zones/${_domain}"; then
if [ "${_code}" = "204" ]; then
return 0
fi
else
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_nederhost_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Authorization: Bearer $NederHost_Key"
export _H2="Content-Type: application/json"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")"
else
response="$(_get "$NederHost_Api/$ep")"
fi
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

181
dnsapi/dns_neodigit.sh

@ -0,0 +1,181 @@
#!/usr/bin/env sh
#
# NEODIGIT_API_TOKEN="jasdfhklsjadhflnhsausdfas"
# This is Neodigit.net api wrapper for acme.sh
#
# Author: Adrian Almenar
# Report Bugs here: https://github.com/tecnocratica/acme.sh
#
NEODIGIT_API_URL="https://api.neodigit.net/v1"
#
######## Public functions #####################
# Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_neodigit_add() {
fulldomain=$1
txtvalue=$2
NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}"
if [ -z "$NEODIGIT_API_TOKEN" ]; then
NEODIGIT_API_TOKEN=""
_err "You haven't specified a Token api key."
_err "Please create the key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN"
_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"
_debug "Getting txt records"
_neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain"
_debug _code "$_code"
if [ "$_code" != "200" ]; then
_err "error retrieving data!"
return 1
fi
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug domain "$_domain"
_debug sub_domain "$_sub_domain"
_info "Adding record"
if _neo_rest POST "dns/zones/$_domain_id/records" "{\"record\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":60}}"; then
if printf -- "%s" "$response" | grep "$_sub_domain" >/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
}
#fulldomain txtvalue
dns_neodigit_rm() {
fulldomain=$1
txtvalue=$2
NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}"
if [ -z "$NEODIGIT_API_TOKEN" ]; then
NEODIGIT_API_TOKEN=""
_err "You haven't specified a Token api key."
_err "Please create the key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN"
_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"
_neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain&content=$txtvalue"
if [ "$_code" != "200" ]; then
_err "error retrieving data!"
return 1
fi
record_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _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 ! _neo_rest DELETE "dns/zones/$_domain_id/records/$record_id"; then
_err "Delete record error."
return 1
fi
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=dasfdsafsadg5ythd
_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 ! _neo_rest GET "dns/zones?name=$h"; then
return 1
fi
_debug p "$p"
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1)
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
}
_neo_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="X-TCPanel-Token: $NEODIGIT_API_TOKEN"
export _H2="Content-Type: application/json"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$NEODIGIT_API_URL/$ep" "" "$m")"
else
response="$(_get "$NEODIGIT_API_URL/$ep")"
fi
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

134
dnsapi/dns_netcup.sh

@ -0,0 +1,134 @@
#!/usr/bin/env sh
#developed by linux-insideDE
NC_Apikey="${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}"
NC_Apipw="${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}"
NC_CID="${NC_CID:-$(_readaccountconf_mutable NC_CID)}"
end="https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
client=""
dns_netcup_add() {
_debug NC_Apikey "$NC_Apikey"
login
if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then
_err "No Credentials given"
return 1
fi
_saveaccountconf_mutable NC_Apikey "$NC_Apikey"
_saveaccountconf_mutable NC_Apipw "$NC_Apipw"
_saveaccountconf_mutable NC_CID "$NC_CID"
fulldomain=$1
txtvalue=$2
domain=""
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
exit=$(_math "$exit" + 1)
i=$exit
while
[ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
domain="$tmp"
else
domain="$tmp.$domain"
fi
if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"false\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
_debug "$msg"
if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
else
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
logout
}
dns_netcup_rm() {
login
fulldomain=$1
txtvalue=$2
domain=""
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
exit=$(_math "$exit" + 1)
i=$exit
rec=""
while
[ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
domain="$tmp"
else
domain="$tmp.$domain"
fi
if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
msg=$(_post "{\"action\": \"infoDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\", \"domainname\": \"$domain\"}}" "$end" "" "POST")
rec=$(echo "$msg" | sed 's/\[//g' | sed 's/\]//g' | sed 's/{\"serverrequestid\".*\"dnsrecords\"://g' | sed 's/},{/};{/g' | sed 's/{//g' | sed 's/}//g')
_debug "$msg"
if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
else
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
ida=0000
idv=0001
ids=0000000000
i=1
while
[ "$i" -ne 0 ]
do
specrec=$(_getfield "$rec" "$i" ";")
idv="$ida"
ida=$(_getfield "$specrec" "1" "," | sed 's/\"id\":\"//g' | sed 's/\"//g')
txtv=$(_getfield "$specrec" "5" "," | sed 's/\"destination\":\"//g' | sed 's/\"//g')
i=$(_math "$i" + 1)
if [ "$txtvalue" = "$txtv" ]; then
i=0
ids="$ida"
fi
if [ "$ida" = "$idv" ]; then
i=0
fi
done
msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"$ids\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"TRUE\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
_debug "$msg"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
logout
}
login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
_debug "$tmp"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
}
logout() {
tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
_debug "$tmp"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
}

205
dnsapi/dns_nic.sh

@ -0,0 +1,205 @@
#!/usr/bin/env sh
#
#NIC_ClientID='0dc0xxxxxxxxxxxxxxxxxxxxxxxxce88'
#NIC_ClientSecret='3LTtxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxnuW8'
#NIC_Username="000000/NIC-D"
#NIC_Password="xxxxxxx"
NIC_Api="https://api.nic.ru"
dns_nic_add() {
fulldomain="${1}"
txtvalue="${2}"
if ! _nic_get_authtoken save; then
_err "get NIC auth token failed"
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 _service "$_service"
_info "Adding record"
if ! _nic_rest PUT "services/$_service/zones/$_domain/records" "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><request><rr-list><rr><name>$_sub_domain</name><type>TXT</type><txt><string>$txtvalue</string></txt></rr></rr-list></request>"; then
_err "Add TXT record error"
return 1
fi
if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
return 1
fi
_info "Added, OK"
}
dns_nic_rm() {
fulldomain="${1}"
txtvalue="${2}"
if ! _nic_get_authtoken; then
_err "get NIC auth token failed"
return 1
fi
if ! _get_root "$fulldomain"; then
_err "Invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug _service "$_service"
if ! _nic_rest GET "services/$_service/zones/$_domain/records"; then
_err "Get records error"
return 1
fi
_domain_id=$(printf "%s" "$response" | grep "$_sub_domain" | grep -- "$txtvalue" | sed -r "s/.*<rr id=\"(.*)\".*/\1/g")
if ! _nic_rest DELETE "services/$_service/zones/$_domain/records/$_domain_id"; then
_err "Delete record error"
return 1
fi
if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
return 1
fi
}
#################### Private functions below ##################################
#_nic_get_auth_elements [need2save]
_nic_get_auth_elements() {
_need2save=$1
NIC_ClientID="${NIC_ClientID:-$(_readaccountconf_mutable NIC_ClientID)}"
NIC_ClientSecret="${NIC_ClientSecret:-$(_readaccountconf_mutable NIC_ClientSecret)}"
NIC_Username="${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}"
NIC_Password="${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}"
## for backward compatibility
if [ -z "$NIC_ClientID" ] || [ -z "$NIC_ClientSecret" ]; then
NIC_Token="${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}"
_debug NIC_Token "$NIC_Token"
if [ -n "$NIC_Token" ]; then
_two_values="$(echo "${NIC_Token}" | _dbase64)"
_debug _two_values "$_two_values"
NIC_ClientID=$(echo "$_two_values" | cut -d':' -f1)
NIC_ClientSecret=$(echo "$_two_values" | cut -d':' -f2-)
_debug restored_NIC_ClientID "$NIC_ClientID"
_debug restored_NIC_ClientSecret "$NIC_ClientSecret"
fi
fi
if [ -z "$NIC_ClientID" ] || [ -z "$NIC_ClientSecret" ] || [ -z "$NIC_Username" ] || [ -z "$NIC_Password" ]; then
NIC_ClientID=""
NIC_ClientSecret=""
NIC_Username=""
NIC_Password=""
_err "You must export variables: NIC_ClientID, NIC_ClientSecret, NIC_Username and NIC_Password"
return 1
fi
if [ "$_need2save" ]; then
_saveaccountconf_mutable NIC_ClientID "$NIC_ClientID"
_saveaccountconf_mutable NIC_ClientSecret "$NIC_ClientSecret"
_saveaccountconf_mutable NIC_Username "$NIC_Username"
_saveaccountconf_mutable NIC_Password "$NIC_Password"
fi
NIC_BasicAuth=$(printf "%s:%s" "${NIC_ClientID}" "${NIC_ClientSecret}" | _base64)
_debug NIC_BasicAuth "$NIC_BasicAuth"
}
#_nic_get_authtoken [need2save]
_nic_get_authtoken() {
_need2save=$1
if ! _nic_get_auth_elements "$_need2save"; then
return 1
fi
_info "Getting NIC auth token"
export _H1="Authorization: Basic ${NIC_BasicAuth}"
export _H2="Content-Type: application/x-www-form-urlencoded"
res=$(_post "grant_type=password&username=${NIC_Username}&password=${NIC_Password}&scope=%28GET%7CPUT%7CPOST%7CDELETE%29%3A%2Fdns-master%2F.%2B" "$NIC_Api/oauth/token" "" "POST")
if _contains "$res" "access_token"; then
_auth_token=$(printf "%s" "$res" | cut -d , -f2 | tr -d "\"" | sed "s/access_token://")
_info "Token received"
_debug _auth_token "$_auth_token"
return 0
fi
return 1
}
_get_root() {
domain="$1"
i=1
p=1
if ! _nic_rest GET "zones"; then
return 1
fi
_all_domains=$(printf "%s" "$response" | grep "idn-name" | sed -r "s/.*idn-name=\"(.*)\" name=.*/\1/g")
_debug2 _all_domains "$_all_domains"
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
_debug h "$h"
if [ -z "$h" ]; then
return 1
fi
if _contains "$_all_domains" "^$h$"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
_service=$(printf "%s" "$response" | grep "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/")
return 0
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
_nic_rest() {
m="$1"
ep="$2"
data="$3"
_debug "$ep"
export _H1="Content-Type: application/xml"
export _H2="Authorization: Bearer $_auth_token"
if [ "$m" != "GET" ]; then
_debug data "$data"
response=$(_post "$data" "$NIC_Api/dns-master/$ep" "" "$m")
else
response=$(_get "$NIC_Api/dns-master/$ep")
fi
if _contains "$response" "<errors>"; then
error=$(printf "%s" "$response" | grep "error code" | sed -r "s/.*<error code=.*>(.*)<\/error>/\1/g")
_err "Error: $error"
return 1
fi
if ! _contains "$response" "<status>success</status>"; then
return 1
fi
_debug2 response "$response"
return 0
}

88
dnsapi/dns_nm.sh

@ -0,0 +1,88 @@
#!/usr/bin/env sh
########################################################################
# https://namemaster.de hook script for acme.sh
#
# Environment variables:
#
# - $NM_user (your namemaster.de API username)
# - $NM_sha256 (your namemaster.de API password_as_sha256hash)
#
# Author: Thilo Gass <thilo.gass@gmail.com>
# Git repo: https://github.com/ThiloGa/acme.sh
#-- dns_nm_add() - Add TXT record --------------------------------------
# Usage: dns_nm_add _acme-challenge.subdomain.domain.com "XyZ123..."
namemaster_api="https://namemaster.de/api/api.php"
dns_nm_add() {
fulldomain=$1
txt_value=$2
_info "Using DNS-01 namemaster hook"
NM_user="${NM_user:-$(_readaccountconf_mutable NM_user)}"
NM_sha256="${NM_sha256:-$(_readaccountconf_mutable NM_sha256)}"
if [ -z "$NM_user" ] || [ -z "$NM_sha256" ]; then
NM_user=""
NM_sha256=""
_err "No auth details provided. Please set user credentials using the \$NM_user and \$NM_sha256 environment variables."
return 1
fi
#save the api user and sha256 password to the account conf file.
_debug "Save user and hash"
_saveaccountconf_mutable NM_user "$NM_user"
_saveaccountconf_mutable NM_sha256 "$NM_sha256"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain" "$fulldomain"
return 1
fi
_info "die Zone lautet:" "$zone"
get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Antwort=csv&Typ=ACME&zone=$zone&hostname=$fulldomain&TXT=$txt_value&Action=Auto&Lifetime=3600"
if ! erg="$(_get "$get")"; then
_err "error Adding $fulldomain TXT: $txt_value"
return 1
fi
if _contains "$erg" "Success"; then
_info "Success, TXT Added, OK"
else
_err "error Adding $fulldomain TXT: $txt_value erg: $erg"
return 1
fi
_debug "ok Auto $fulldomain TXT: $txt_value erg: $erg"
return 0
}
dns_nm_rm() {
fulldomain=$1
txtvalue=$2
_info "TXT enrty in $fulldomain is deleted automatically"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
}
_get_root() {
domain=$1
get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Typ=acme&hostname=$domain&Action=getzone&antwort=csv"
if ! zone="$(_get "$get")"; then
_err "error getting Zone"
return 1
else
if _contains "$zone" "hostname not found"; then
return 1
fi
fi
}

64
dnsapi/dns_nsd.sh

@ -0,0 +1,64 @@
#!/usr/bin/env sh
#Nsd_ZoneFile="/etc/nsd/zones/example.com.zone"
#Nsd_Command="sudo nsd-control reload"
# args: fulldomain txtvalue
dns_nsd_add() {
fulldomain=$1
txtvalue=$2
ttlvalue=300
Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
# Arg checks
if [ -z "$Nsd_ZoneFile" ] || [ -z "$Nsd_Command" ]; then
Nsd_ZoneFile=""
Nsd_Command=""
_err "Specify ENV vars Nsd_ZoneFile and Nsd_Command"
return 1
fi
if [ ! -f "$Nsd_ZoneFile" ]; then
Nsd_ZoneFile=""
Nsd_Command=""
_err "No such file: $Nsd_ZoneFile"
return 1
fi
_savedomainconf Nsd_ZoneFile "$Nsd_ZoneFile"
_savedomainconf Nsd_Command "$Nsd_Command"
echo "$fulldomain. $ttlvalue IN TXT \"$txtvalue\"" >>"$Nsd_ZoneFile"
_info "Added TXT record for $fulldomain"
_debug "Running $Nsd_Command"
if eval "$Nsd_Command"; then
_info "Successfully updated the zone"
return 0
else
_err "Problem updating the zone"
return 1
fi
}
# args: fulldomain txtvalue
dns_nsd_rm() {
fulldomain=$1
txtvalue=$2
ttlvalue=300
Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
sed -i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
_info "Removed TXT record for $fulldomain"
_debug "Running $Nsd_Command"
if eval "$Nsd_Command"; then
_info "Successfully reloaded NSD "
return 0
else
_err "Problem reloading NSD"
return 1
fi
}

8
dnsapi/dns_nsone.sh

@ -46,7 +46,7 @@ dns_nsone_add() {
if [ "$count" = "0" ]; then if [ "$count" = "0" ]; then
_info "Adding record" _info "Adding record"
if _nsone_rest PUT "zones/$_domain/$fulldomain/TXT" "{\"answers\":[{\"answer\":[\"$txtvalue\"]}],\"type\":\"TXT\",\"domain\":\"$fulldomain\",\"zone\":\"$_domain\"}"; then
if _nsone_rest PUT "zones/$_domain/$fulldomain/TXT" "{\"answers\":[{\"answer\":[\"$txtvalue\"]}],\"type\":\"TXT\",\"domain\":\"$fulldomain\",\"zone\":\"$_domain\",\"ttl\":0}"; then
if _contains "$response" "$fulldomain"; then if _contains "$response" "$fulldomain"; then
_info "Added" _info "Added"
#todo: check if the record takes effect #todo: check if the record takes effect
@ -59,10 +59,10 @@ dns_nsone_add() {
_err "Add txt record error." _err "Add txt record error."
else else
_info "Updating record" _info "Updating record"
record_id=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain.\",[^{]*\"type\":\"TXT\",\"id\":\"[^,]*\"" | _head_n 1 | cut -d: -f7 | cut -d, -f1)
_debug "record_id" "$record_id"
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\"]}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\"}"
_nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]},{\"answer\": $prev_txt}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\",\"ttl\":0}"
if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then
_info "Updated!" _info "Updated!"
#todo: check if the record takes effect #todo: check if the record takes effect

54
dnsapi/dns_nsupdate.sh

@ -6,17 +6,39 @@
dns_nsupdate_add() { dns_nsupdate_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
NSUPDATE_SERVER="${NSUPDATE_SERVER:-$(_readaccountconf_mutable NSUPDATE_SERVER)}"
NSUPDATE_SERVER_PORT="${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}"
NSUPDATE_KEY="${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}"
NSUPDATE_ZONE="${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}"
_checkKeyFile || return 1 _checkKeyFile || return 1
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
# save the dns server and key to the account conf file. # save the dns server and key to the account conf file.
_saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
_saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
_saveaccountconf_mutable NSUPDATE_SERVER "${NSUPDATE_SERVER}"
_saveaccountconf_mutable NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
_saveaccountconf_mutable NSUPDATE_KEY "${NSUPDATE_KEY}"
_saveaccountconf_mutable NSUPDATE_ZONE "${NSUPDATE_ZONE}"
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
_info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
nsupdate -k "${NSUPDATE_KEY}" <<EOF
server ${NSUPDATE_SERVER}
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
if [ -z "${NSUPDATE_ZONE}" ]; then
nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
update add ${fulldomain}. 60 in txt "${txtvalue}"
send
EOF
else
nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
zone ${NSUPDATE_ZONE}.
update add ${fulldomain}. 60 in txt "${txtvalue}" update add ${fulldomain}. 60 in txt "${txtvalue}"
send send
EOF EOF
fi
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
_err "error updating domain" _err "error updating domain"
return 1 return 1
@ -28,14 +50,32 @@ EOF
#Usage: dns_nsupdate_rm _acme-challenge.www.domain.com #Usage: dns_nsupdate_rm _acme-challenge.www.domain.com
dns_nsupdate_rm() { dns_nsupdate_rm() {
fulldomain=$1 fulldomain=$1
NSUPDATE_SERVER="${NSUPDATE_SERVER:-$(_readaccountconf_mutable NSUPDATE_SERVER)}"
NSUPDATE_SERVER_PORT="${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}"
NSUPDATE_KEY="${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}"
NSUPDATE_ZONE="${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}"
_checkKeyFile || return 1 _checkKeyFile || return 1
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
_info "removing ${fulldomain}. txt" _info "removing ${fulldomain}. txt"
nsupdate -k "${NSUPDATE_KEY}" <<EOF
server ${NSUPDATE_SERVER}
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
if [ -z "${NSUPDATE_ZONE}" ]; then
nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
update delete ${fulldomain}. txt update delete ${fulldomain}. txt
send send
EOF EOF
else
nsupdate -k "${NSUPDATE_KEY}" $nsdebug <<EOF
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
zone ${NSUPDATE_ZONE}.
update delete ${fulldomain}. txt
send
EOF
fi
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
_err "error updating domain" _err "error updating domain"
return 1 return 1

211
dnsapi/dns_nw.sh

@ -0,0 +1,211 @@
#!/usr/bin/env sh
########################################################################
# NocWorx script for acme.sh
#
# Handles DNS Updates for the Following vendors:
# - Nexcess.net
# - Thermo.io
# - Futurehosting.com
#
# Environment variables:
#
# - NW_API_TOKEN (Your API Token)
# - NW_API_ENDPOINT (One of the following listed below)
#
# Endpoints:
# - https://portal.nexcess.net (default)
# - https://core.thermo.io
# - https://my.futurehosting.com
#
# Note: If you do not have an API token, one can be generated at one
# of the following URLs:
# - https://portal.nexcess.net/api-token
# - https://core.thermo.io/api-token
# - https://my.futurehosting.com/api-token
#
# Author: Frank Laszlo <flaszlo@nexcess.net>
NW_API_VERSION="0"
# dns_nw_add() - Add TXT record
# Usage: dns_nw_add _acme-challenge.subdomain.domain.com "XyZ123..."
dns_nw_add() {
host="${1}"
txtvalue="${2}"
_debug host "${host}"
_debug txtvalue "${txtvalue}"
if ! _check_nw_api_creds; then
return 1
fi
_info "Using NocWorx (${NW_API_ENDPOINT})"
_debug "Calling: dns_nw_add() '${host}' '${txtvalue}'"
_debug "Detecting root zone"
if ! _get_root "${host}"; then
_err "Zone for domain does not exist."
return 1
fi
_debug _zone_id "${_zone_id}"
_debug _sub_domain "${_sub_domain}"
_debug _domain "${_domain}"
_post_data="{\"zone_id\": \"${_zone_id}\", \"type\": \"TXT\", \"host\": \"${host}\", \"target\": \"${txtvalue}\", \"ttl\": \"300\"}"
if _rest POST "dns-record" "${_post_data}" && [ -n "${response}" ]; then
_record_id=$(printf "%s\n" "${response}" | _egrep_o "\"record_id\": *[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
_debug _record_id "${_record_id}"
if [ -z "$_record_id" ]; then
_err "Error adding the TXT record."
return 1
fi
_info "TXT record successfully added."
return 0
fi
return 1
}
# dns_nw_rm() - Remove TXT record
# Usage: dns_nw_rm _acme-challenge.subdomain.domain.com "XyZ123..."
dns_nw_rm() {
host="${1}"
txtvalue="${2}"
_debug host "${host}"
_debug txtvalue "${txtvalue}"
if ! _check_nw_api_creds; then
return 1
fi
_info "Using NocWorx (${NW_API_ENDPOINT})"
_debug "Calling: dns_nw_rm() '${host}'"
_debug "Detecting root zone"
if ! _get_root "${host}"; then
_err "Zone for domain does not exist."
return 1
fi
_debug _zone_id "${_zone_id}"
_debug _sub_domain "${_sub_domain}"
_debug _domain "${_domain}"
_parameters="?zone_id=${_zone_id}"
if _rest GET "dns-record" "${_parameters}" && [ -n "${response}" ]; then
response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"record_id":/|"record_id":/g' | sed 's/|/&{/g' | tr "|" "\n")"
_debug response "${response}"
record="$(echo "${response}" | _egrep_o "{.*\"host\": *\"${_sub_domain}\", *\"target\": *\"${txtvalue}\".*}")"
_debug record "${record}"
if [ "${record}" ]; then
_record_id=$(printf "%s\n" "${record}" | _egrep_o "\"record_id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "${_record_id}" ]; then
_debug _record_id "${_record_id}"
_rest DELETE "dns-record/${_record_id}"
_info "TXT record successfully deleted."
return 0
fi
return 1
fi
return 0
fi
return 1
}
_check_nw_api_creds() {
NW_API_TOKEN="${NW_API_TOKEN:-$(_readaccountconf_mutable NW_API_TOKEN)}"
NW_API_ENDPOINT="${NW_API_ENDPOINT:-$(_readaccountconf_mutable NW_API_ENDPOINT)}"
if [ -z "${NW_API_ENDPOINT}" ]; then
NW_API_ENDPOINT="https://portal.nexcess.net"
fi
if [ -z "${NW_API_TOKEN}" ]; then
_err "You have not defined your NW_API_TOKEN."
_err "Please create your token and try again."
_err "If you need to generate a new token, please visit one of the following URLs:"
_err " - https://portal.nexcess.net/api-token"
_err " - https://core.thermo.io/api-token"
_err " - https://my.futurehosting.com/api-token"
return 1
fi
_saveaccountconf_mutable NW_API_TOKEN "${NW_API_TOKEN}"
_saveaccountconf_mutable NW_API_ENDPOINT "${NW_API_ENDPOINT}"
}
_get_root() {
domain="${1}"
i=2
p=1
if _rest GET "dns-zone"; then
response="$(echo "${response}" | tr -d "\n" | sed 's/^\[\(.*\)\]$/\1/' | sed -e 's/{"zone_id":/|"zone_id":/g' | sed 's/|/&{/g' | tr "|" "\n")"
_debug response "${response}"
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\": *\"${h}\".*}")"
if [ "${hostedzone}" ]; then
_zone_id=$(printf "%s\n" "${hostedzone}" | _egrep_o "\"zone_id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "${_zone_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
}
_rest() {
method="${1}"
ep="/${2}"
data="${3}"
_debug method "${method}"
_debug ep "${ep}"
export _H1="Accept: application/json"
export _H2="Content-Type: application/json"
export _H3="Api-Version: ${NW_API_VERSION}"
export _H4="User-Agent: NW-ACME-CLIENT"
export _H5="Authorization: Bearer ${NW_API_TOKEN}"
if [ "${method}" != "GET" ]; then
_debug data "${data}"
response="$(_post "${data}" "${NW_API_ENDPOINT}${ep}" "" "${method}")"
else
response="$(_get "${NW_API_ENDPOINT}${ep}${data}")"
fi
if [ "${?}" != "0" ]; then
_err "error ${ep}"
return 1
fi
_debug2 response "${response}"
return 0
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save