Browse Source

Merge branch 'dev' into Add-AWS_API-slowrate

none
Stephane Moser 5 years ago
parent
commit
37978b4fe5
  1. 12
      .github/FUNDING.yml
  2. 4
      .github/ISSUE_TEMPLATE.md
  3. 8
      .github/PULL_REQUEST_TEMPLATE.md
  4. 2
      .travis.yml
  5. 6
      Dockerfile
  6. 674
      LICENSE.md
  7. 80
      README.md
  8. 666
      acme.sh
  9. 287
      deploy/docker.sh
  10. 19
      deploy/gcore_cdn.sh
  11. 282
      deploy/haproxy.sh
  12. 111
      deploy/routeros.sh
  13. 2
      dnsapi/README.md
  14. 83
      dnsapi/dns_acmeproxy.sh
  15. 2
      dnsapi/dns_active24.sh
  16. 2
      dnsapi/dns_ali.sh
  17. 24
      dnsapi/dns_aws.sh
  18. 4
      dnsapi/dns_azure.sh
  19. 60
      dnsapi/dns_cf.sh
  20. 8
      dnsapi/dns_cx.sh
  21. 130
      dnsapi/dns_ddnss.sh
  22. 8
      dnsapi/dns_desec.sh
  23. 155
      dnsapi/dns_domeneshop.sh
  24. 2
      dnsapi/dns_dp.sh
  25. 176
      dnsapi/dns_durabledns.sh
  26. 271
      dnsapi/dns_freedns.sh
  27. 8
      dnsapi/dns_gcloud.sh
  28. 12
      dnsapi/dns_he.sh
  29. 156
      dnsapi/dns_hexonet.sh
  30. 20
      dnsapi/dns_hostingde.sh
  31. 180
      dnsapi/dns_internetbs.sh
  32. 286
      dnsapi/dns_jd.sh
  33. 149
      dnsapi/dns_leaseweb.sh
  34. 3
      dnsapi/dns_linode_v4.sh
  35. 60
      dnsapi/dns_loopia.sh
  36. 69
      dnsapi/dns_maradns.sh
  37. 210
      dnsapi/dns_miab.sh
  38. 2
      dnsapi/dns_myapi.sh
  39. 2
      dnsapi/dns_namecheap.sh
  40. 11
      dnsapi/dns_namesilo.sh
  41. 2
      dnsapi/dns_netcup.sh
  42. 185
      dnsapi/dns_nic.sh
  43. 64
      dnsapi/dns_nsd.sh
  44. 24
      dnsapi/dns_nsupdate.sh
  45. 179
      dnsapi/dns_one.sh
  46. 2
      dnsapi/dns_ovh.sh
  47. 414
      dnsapi/dns_pleskxml.sh
  48. 224
      dnsapi/dns_rcode0.sh
  49. 63
      dnsapi/dns_regru.sh
  50. 261
      dnsapi/dns_schlundtech.sh
  51. 147
      dnsapi/dns_variomedia.sh
  52. 2
      dnsapi/dns_vscale.sh
  53. 161
      dnsapi/dns_vultr.sh
  54. 15
      dnsapi/dns_yandex.sh
  55. 68
      notify/dingtalk.sh
  56. 86
      notify/ifttt.sh
  57. 138
      notify/mail.sh
  58. 131
      notify/mailgun.sh
  59. 15
      notify/pop.sh
  60. 58
      notify/postmark.sh
  61. 63
      notify/pushover.sh
  62. 56
      notify/sendgrid.sh
  63. 55
      notify/slack.sh
  64. 15
      notify/smtp.sh
  65. 90
      notify/xmpp.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']

4
.github/ISSUE_TEMPLATE.md

@ -1,5 +1,7 @@
<!-- <!--
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试输出,我帮不了你.
我很忙, 每天可能只有 几秒钟 时间看你的 issue, 如果不按照我的要求写 issue, 你可能不会得到任何回复, 石沉大海.
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试信息. 我做不了什么.
如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh 如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
If it is a bug report: If it is a bug report:

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/Neilpang/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.
--> -->

2
.travis.yml

@ -28,7 +28,7 @@ script:
- 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/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
- if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi - if [ "$TRAVIS_OS_NAME" = "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

6
Dockerfile

@ -1,13 +1,16 @@
FROM alpine:3.9
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 \ coreutils \
bind-tools \ bind-tools \
curl \ curl \
socat \ socat \
tzdata \ 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
@ -48,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>.

80
README.md

@ -1,6 +1,6 @@
# 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/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)
[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<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 v1 and ACME v2
@ -13,6 +13,7 @@
- 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.
@ -45,25 +46,25 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
| 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
|1|[![](https://neilpang.github.io/acmetest/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu
|2|[![](https://neilpang.github.io/acmetest/status/debian-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian
|3|[![](https://neilpang.github.io/acmetest/status/centos-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS
|4|[![](https://neilpang.github.io/acmetest/status/windows-cygwin.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
|5|[![](https://neilpang.github.io/acmetest/status/freebsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD
|6|[![](https://neilpang.github.io/acmetest/status/pfsense.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense
|7|[![](https://neilpang.github.io/acmetest/status/opensuse-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE
|8|[![](https://neilpang.github.io/acmetest/status/alpine-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl)
|9|[![](https://neilpang.github.io/acmetest/status/base-archlinux.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux
|10|[![](https://neilpang.github.io/acmetest/status/fedora-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora
|11|[![](https://neilpang.github.io/acmetest/status/kalilinux-kali-linux-docker.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux
|12|[![](https://neilpang.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux
|13|[![](https://neilpang.github.io/acmetest/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 |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
|15|[![](https://neilpang.github.io/acmetest/status/openbsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD
|16|[![](https://neilpang.github.io/acmetest/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) |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
|18|[![](https://neilpang.github.io/acmetest/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris
|19|[![](https://neilpang.github.io/acmetest/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 |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): For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest):
@ -432,20 +433,55 @@ acme.sh --upgrade --auto-upgrade 0
https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
# 16. Under the Hood
# 16. Send notifications in cronjob
https://github.com/Neilpang/acme.sh/wiki/notify
# 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:
# 17. 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
# 18. License & Others
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/Neilpang/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
@ -454,7 +490,7 @@ 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/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome.
# 19. 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/)

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

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/Neilpang/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
}

19
deploy/gcore_cdn.sh

@ -1,7 +1,6 @@
#!/usr/bin/env sh #!/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/). # 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/).
# Uses command line curl for send requests and jq for parse responses.
# Returns 0 when success. # Returns 0 when success.
# #
# Written by temoffey <temofffey@gmail.com> # Written by temoffey <temofffey@gmail.com>
@ -27,8 +26,8 @@ gcore_cdn_deploy() {
_debug _cca "$_cca" _debug _cca "$_cca"
_debug _cfullchain "$_cfullchain" _debug _cfullchain "$_cfullchain"
_fullchain=$(tr '\n\r' '@#' <"$_cfullchain" | sed 's/@/\\n/g;s/#/\\r/g')
_key=$(tr '\n\r' '@#' <"$_ckey" | sed 's/@/\\n/g;s/#/\\r/g')
_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 _fullchain "$_fullchain"
_debug _key "$_key" _debug _key "$_key"
@ -74,19 +73,19 @@ gcore_cdn_deploy() {
_response=$(_get "https://api.gcdn.co/resources") _response=$(_get "https://api.gcdn.co/resources")
_debug _response "$_response" _debug _response "$_response"
_regex=".*(\"id\".*?\"cname\":\"$_cdomain\".*?})" _regex=".*(\"id\".*?\"cname\":\"$_cdomain\".*?})"
_regex="\"cname\":\"$_cdomain\""
_regex="^.*\"cname\":\"$_cdomain\".*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_resource=$(echo "$_response" | sed 's/},{/},\n{/g' | grep -E "$_regex")
_resource=$(echo "$_response" | sed 's/},{/},\n{/g' | _egrep_o "$_regex")
_debug _resource "$_resource" _debug _resource "$_resource"
_regex=".*\"id\":\([0-9]*\),.*$"
_regex=".*\"id\":\([0-9]*\).*\"rules\".*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _resourceId "$_resourceId" _debug _resourceId "$_resourceId"
_regex=".*\"sslData\":\([0-9]*\)}.*$"
_regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _sslDataOld "$_sslDataOld" _debug _sslDataOld "$_sslDataOld"
_regex=".*\"originGroup\":\([0-9]*\),.*$"
_regex=".*\"originGroup\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _originGroup "$_originGroup" _debug _originGroup "$_originGroup"
@ -102,7 +101,7 @@ gcore_cdn_deploy() {
_debug _request "$_request" _debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/sslData") _response=$(_post "$_request" "https://api.gcdn.co/sslData")
_debug _response "$_response" _debug _response "$_response"
_regex=".*\"id\":\([0-9]*\),.*$"
_regex=".*\"id\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p") _sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _sslDataAdd "$_sslDataAdd" _debug _sslDataAdd "$_sslDataAdd"
@ -117,7 +116,7 @@ gcore_cdn_deploy() {
_debug _request "$_request" _debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT") _response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT")
_debug _response "$_response" _debug _response "$_response"
_regex=".*\"sslData\":\([0-9]*\)}.*$"
_regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex" _debug _regex "$_regex"
_sslDataNew=$(echo "$_response" | sed -n "s/$_regex/\1/p") _sslDataNew=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _sslDataNew "$_sslDataNew" _debug _sslDataNew "$_sslDataNew"

282
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,45 +47,226 @@ 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"
# handle reload preference
DEFAULT_HAPROXY_RELOAD="/usr/sbin/service haproxy restart"
if [ -z "${DEPLOY_HAPROXY_RELOAD}" ]; then
_reload="${DEFAULT_HAPROXY_RELOAD}"
_cleardomainconf DEPLOY_HAPROXY_RELOAD
else
_reload="${DEPLOY_HAPROXY_RELOAD}"
_savedomainconf DEPLOY_HAPROXY_RELOAD "$DEPLOY_HAPROXY_RELOAD"
if [ -f "${DOMAIN_CONF}" ]; then
# shellcheck disable=SC1090
. "${DOMAIN_CONF}"
fi fi
_savedomainconf DEPLOY_HAPROXY_PEM_PATH "$DEPLOY_HAPROXY_PEM_PATH"
# work out the path where the PEM file should go
_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
if [ -z "$_pem_path" ]; then
_err "Path to save PEM file not found. Please define DEPLOY_HAPROXY_PEM_PATH."
_debug _cdomain "${_cdomain}"
_debug _ckey "${_ckey}"
_debug _ccert "${_ccert}"
_debug _cca "${_cca}"
_debug _cfullchain "${_cfullchain}"
# 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 fi
_pem_full_path="$_pem_path/$_cdomain.pem"
_info "Full path to PEM $_pem_full_path"
# combine the key and fullchain into a single pem and install
cat "$_cfullchain" "$_ckey" >"$_pem_full_path"
chmod 600 "$_pem_full_path"
_info "Certificate successfully deployed"
# 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
# restart HAProxy
_info "Run reload: $_reload"
if eval "$_reload"; then
_info "Reload success!"
return 0
# 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 else
_err "Reload error"
return 1
_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 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"
# Request the OCSP response from the issuer and store it
if [ "${_subjectdn}" = "${_issuerdn}" ]; then
# If the issuer is a CA cert then our command line has "-CAfile" added
openssl ocsp \
-issuer "${_issuer}" \
-cert "${_pem}" \
-url "${_ocsp_url}" \
-header Host "${_ocsp_host}" \
-respout "${_ocsp}" \
-verify_other "${_issuer}" \
-no_nonce \
-CAfile "${_issuer}" \
| grep -q "${_pem}: good"
_ret=$?
else
# Issuer is not a root CA so no "-CAfile" option
openssl ocsp \
-issuer "${_issuer}" \
-cert "${_pem}" \
-url "${_ocsp_url}" \
-header Host "${_ocsp_host}" \
-respout "${_ocsp}" \
-verify_other "${_issuer}" \
-no_nonce \
| grep -q "${_pem}: good"
_ret=$?
fi
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
} }

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
}

2
dnsapi/README.md

@ -1,4 +1,6 @@
# How to use DNS API # How to use DNS API
DNS api usage: DNS api usage:
https://github.com/Neilpang/acme.sh/wiki/dnsapi https://github.com/Neilpang/acme.sh/wiki/dnsapi

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 ##################################

2
dnsapi/dns_active24.sh

@ -129,7 +129,7 @@ _active24_init() {
return 1 return 1
fi fi
_saveaccountconf_mutable ACTIVE24_Token "ACTIVE24_Token"
_saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token"
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then

2
dnsapi/dns_ali.sh

@ -185,7 +185,7 @@ _clean() {
return 1 return 1
fi fi
record_id="$(echo "$response" | tr '{' "\n" | grep "$_sub_domain" | grep "$txtvalue" | tr "," "\n" | grep RecordId | cut -d '"' -f 4)"
record_id="$(echo "$response" | tr '{' "\n" | grep "$_sub_domain" | grep -- "$txtvalue" | tr "," "\n" | grep RecordId | cut -d '"' -f 4)"
_debug2 record_id "$record_id" _debug2 record_id "$record_id"
if [ -z "$record_id" ]; then if [ -z "$record_id" ]; then

24
dnsapi/dns_aws.sh

@ -6,6 +6,8 @@
#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"
@ -44,14 +46,16 @@ dns_aws_add() {
_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"
_info "Geting existing records for $fulldomain"
_info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
_sleep 1
return 1 return 1
fi fi
@ -64,6 +68,7 @@ dns_aws_add() {
if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
_info "The TXT record already exists. Skipping." _info "The TXT record already exists. Skipping."
_sleep 1
return 0 return 0
fi fi
@ -75,11 +80,14 @@ dns_aws_add() {
_info "TXT record updated successfully." _info "TXT record updated successfully."
if [ -n "$slowrateslepp" ]; then if [ -n "$slowrateslepp" ]; then
_info "Slow rate activated: sleeping for $slowrateslepp seconds" _info "Slow rate activated: sleeping for $slowrateslepp seconds"
sleep "$slowrateslepp"
_sleep "$slowrateslepp"
else
_sleep 1
fi fi
return 0 return 0
fi fi
_sleep 1
return 1 return 1
} }
@ -99,6 +107,7 @@ dns_aws_rm() {
_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"
@ -107,6 +116,7 @@ dns_aws_rm() {
_info "Getting existing records for $fulldomain" _info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
_sleep 1
return 1 return 1
fi fi
@ -115,6 +125,7 @@ dns_aws_rm() {
_debug "_resource_record" "$_resource_record" _debug "_resource_record" "$_resource_record"
else else
_debug "no records exist, skip" _debug "no records exist, skip"
_sleep 1
return 0 return 0
fi fi
@ -124,11 +135,14 @@ dns_aws_rm() {
_info "TXT record deleted successfully." _info "TXT record deleted successfully."
if [ -n "$slowrateslepp" ]; then if [ -n "$slowrateslepp" ]; then
_info "Slow rate activated: sleeping for $slowrateslepp seconds" _info "Slow rate activated: sleeping for $slowrateslepp seconds"
sleep "$slowrateslepp"
_sleep "$slowrateslepp"
else
_sleep 1
fi fi
return 0 return 0
fi fi
_sleep 1
return 1 return 1
} }

4
dnsapi/dns_azure.sh

@ -317,7 +317,7 @@ _get_root() {
## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways
## ##
_azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken" _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 is Json response
# Find matching domain name in Json response
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100) h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug2 "Checking domain: $h" _debug2 "Checking domain: $h"
@ -328,7 +328,7 @@ _get_root() {
fi fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then 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 \")
_domain_id=$(echo "$response" | _egrep_o "\\{\"id\":\"[^\"]*\\/$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then
if [ "$i" = 1 ]; then if [ "$i" = 1 ]; then
#create the record at the domain apex (@) if only the domain name was provided as --domain-alias #create the record at the domain apex (@) if only the domain name was provided as --domain-alias

60
dnsapi/dns_cf.sh

@ -5,6 +5,9 @@
# #
#CF_Email="xxxx@sss.com" #CF_Email="xxxx@sss.com"
#CF_Token="xxxx"
#CF_Account_ID="xxxx"
CF_Api="https://api.cloudflare.com/client/v4" CF_Api="https://api.cloudflare.com/client/v4"
######## Public functions ##################### ######## Public functions #####################
@ -14,8 +17,15 @@ 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_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"
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=""
@ -29,10 +39,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
@ -58,7 +68,7 @@ dns_cf_add() {
# if [ "$count" = "0" ]; then # 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 _contains "$response" "$fulldomain"; then
if _contains "$response" "$txtvalue"; then
_info "Added, OK" _info "Added, OK"
return 0 return 0
elif _contains "$response" "The record already exists"; then elif _contains "$response" "The record already exists"; then
@ -71,19 +81,6 @@ dns_cf_add() {
fi fi
_err "Add txt record error." _err "Add txt record error."
return 1 return 1
# else
# _info "Updating record"
# record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
# _debug "record_id" "$record_id"
#
# _cf_rest PUT "zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}"
# if [ "$?" = "0" ]; then
# _info "Updated, OK"
# return 0
# fi
# _err "Update error"
# return 1
# fi
} }
@ -92,15 +89,10 @@ 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_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 didn't specify a Cloudflare api key and email yet."
_err "You can get yours from here https://dash.cloudflare.com/profile."
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 +139,7 @@ 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
while true; do while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100) h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@ -157,11 +149,17 @@ _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
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 \") _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
@ -182,9 +180,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"

8
dnsapi/dns_cx.sh

@ -16,6 +16,8 @@ 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=""
@ -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
@ -43,6 +45,8 @@ dns_cx_add() {
dns_cx_rm() { dns_cx_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 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=""

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://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' | _tail_n 1)"
else
_err "Unsupported method"
return 1
fi
_debug2 response "$response"
return 0
}

8
dnsapi/dns_desec.sh

@ -25,8 +25,8 @@ dns_desec_add() {
if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
DEDYN_TOKEN="" DEDYN_TOKEN=""
DEDYN_NAME="" DEDYN_NAME=""
_err "You don't specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create you key and try again."
_err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create your key and try again."
_err "e.g." _err "e.g."
_err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e" _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
_err "export DEDYN_NAME=foobar.dedyn.io" _err "export DEDYN_NAME=foobar.dedyn.io"
@ -92,8 +92,8 @@ dns_desec_rm() {
if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
DEDYN_TOKEN="" DEDYN_TOKEN=""
DEDYN_NAME="" DEDYN_NAME=""
_err "You don't specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create you key and try again."
_err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
_err "Please create your key and try again."
_err "e.g." _err "e.g."
_err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e" _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
_err "export DEDYN_NAME=foobar.dedyn.io" _err "export DEDYN_NAME=foobar.dedyn.io"

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
}

2
dnsapi/dns_dp.sh

@ -63,7 +63,7 @@ dns_dp_rm() {
return 0 return 0
fi fi
record_id=$(echo "$response" | tr "{" "\n" | grep "$txtvalue" | grep '^"id"' | cut -d : -f 2 | cut -d '"' -f 2)
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."

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
}

271
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/Neilpang/acme.sh/issues/2305
# #
######## Public functions ##################### ######## Public functions #####################
@ -46,75 +47,33 @@ 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"
i="$(_math "$i" + 1)"
top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)"
_debug "sub_domain: $sub_domain" _debug "sub_domain: $sub_domain"
_debug "top_domain: $top_domain"
# Sometimes FreeDNS does not return the subdomain page but rather
# returns a page regarding becoming a premium member. This usually
# happens after a period of inactivity. Immediately trying again
# returns the correct subdomain page. So, we will try twice to
# load the page and obtain our domain ID
attempts=2
while [ "$attempts" -gt "0" ]; do
attempts="$(_math "$attempts" - 1)"
htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
if [ "$?" != "0" ]; then
if [ "$using_cached_cookies" = "true" ]; then
_err "Has your FreeDNS username and password changed? If so..."
_err "Please export as FREEDNS_User / FREEDNS_Password and try again."
fi
return 1
fi
subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '<form .*</form>' | sed 's/<tr>/@<tr>/g' | tr '@' '\n' | grep edit.php | grep "$top_domain")"
_debug3 "subdomain_csv: $subdomain_csv"
# The above beauty ends with striping out rows that do not have an
# href to edit.php and do not have the top domain we are looking for.
# So all we should be left with is CSV of table of subdomains we are
# interested in.
# Now we have to read through this table and extract the data we need
lines="$(echo "$subdomain_csv" | wc -l)"
i=0
found=0
DNSdomainid=""
while [ "$i" -lt "$lines" ]; do
i="$(_math "$i" + 1)"
line="$(echo "$subdomain_csv" | sed -n "${i}p")"
_debug2 "line: $line"
if [ $found = 0 ] && _contains "$line" "<td>$top_domain</td>"; then
# this line will contain DNSdomainid for the top_domain
DNSdomainid="$(echo "$line" | _egrep_o "edit_domain_id *= *.*>" | cut -d = -f 2 | cut -d '>' -f 1)"
_debug2 "DNSdomainid: $DNSdomainid"
found=1
DNSdomainid="$(_freedns_domain_id "$top_domain")"
if [ "$?" = "0" ]; then
_info "Domain $top_domain found at FreeDNS, domain_id $DNSdomainid"
break break
else
_info "Domain $top_domain not found at FreeDNS, try with next level of TLD"
fi fi
done done
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
_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
# Add in new TXT record with the value provided # Add in new TXT record with the value provided
_debug "Adding TXT record for $fulldomain, $txtvalue" _debug "Adding TXT record for $fulldomain, $txtvalue"
@ -134,80 +93,47 @@ 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")"
_debug3 "subdomain_csv: $subdomain_csv"
# The above beauty ends with striping out rows that do not have an
# href to edit.php and do not have the domain name we are looking for.
# So all we should be left with is CSV of table of subdomains we are
# interested in.
# Now we have to read through this table and extract the data we need
lines="$(echo "$subdomain_csv" | wc -l)"
# 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
DNSdataid=""
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")"
_debug3 "line: $line"
DNSname="$(echo "$line" | _egrep_o 'edit.php.*</a>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug2 "DNSname: $DNSname"
if [ "$DNSname" = "$fulldomain" ]; then
DNStype="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '4p' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_debug2 "DNStype: $DNStype"
if [ "$DNStype" = "TXT" ]; then
DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)"
_debug2 "DNSdataid: $DNSdataid"
DNSvalue="$(echo "$line" | sed 's/<td/@<td/g' | tr '@' '\n' | sed -n '5p' | cut -d '>' -f 2 | cut -d '<' -f 1)"
if _startswith "$DNSvalue" "&quot;"; then
# remove the quotation from the start
DNSvalue="$(echo "$DNSvalue" | cut -c 7-)"
fi
if _endswith "$DNSvalue" "..."; then
# value was truncated, remove the dot dot dot from the end
DNSvalue="$(echo "$DNSvalue" | sed 's/...$//')"
elif _endswith "$DNSvalue" "&quot;"; then
# else remove the closing quotation from the end
DNSvalue="$(echo "$DNSvalue" | sed 's/......$//')"
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 fi
_debug2 "DNSvalue: $DNSvalue"
if [ -n "$DNSdataid" ] && _startswith "$txtvalue" "$DNSvalue"; then
# Found a match. But note... Website is truncating the
# value field so we are only testing that part that is not
# truncated. This should be accurate enough.
_debug "Deleting TXT record for $fulldomain, $txtvalue"
_freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"
return $?
return 1
fi 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 $?
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.
_debug3 "$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
} }
@ -271,6 +197,33 @@ _freedns_retrieve_subdomain_page() {
return 0 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"
return 0
}
# usage _freedns_add_txt_record login_cookies domain_id subdomain value # usage _freedns_add_txt_record login_cookies domain_id subdomain value
# returns 0 success # returns 0 success
_freedns_add_txt_record() { _freedns_add_txt_record() {
@ -324,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
}

8
dnsapi/dns_gcloud.sh

@ -93,7 +93,7 @@ _dns_gcloud_execute_tr() {
} }
_dns_gcloud_remove_rrs() { _dns_gcloud_remove_rrs() {
if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \
if ! xargs -r gcloud dns record-sets transaction remove \
--name="$fulldomain." \ --name="$fulldomain." \
--ttl="$ttl" \ --ttl="$ttl" \
--type=TXT \ --type=TXT \
@ -108,7 +108,7 @@ _dns_gcloud_remove_rrs() {
_dns_gcloud_add_rrs() { _dns_gcloud_add_rrs() {
ttl=60 ttl=60
if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \
if ! xargs -r gcloud dns record-sets transaction add \
--name="$fulldomain." \ --name="$fulldomain." \
--ttl="$ttl" \ --ttl="$ttl" \
--type=TXT \ --type=TXT \
@ -134,12 +134,12 @@ _dns_gcloud_find_zone() {
filter="$filter)" filter="$filter)"
_debug filter "$filter" _debug filter "$filter"
# List domains and find the longest match (in case of some levels of delegation)
# 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 \ if ! match=$(gcloud dns managed-zones list \
--format="value(name, dnsName)" \ --format="value(name, dnsName)" \
--filter="$filter" \ --filter="$filter" \
| while read -r dnsName name; do | while read -r dnsName name; do
printf "%s\t%s\t%s\n" "${#dnsName}" "$dnsName" "$name"
printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name"
done \ done \
| sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then | 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?" _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"

12
dnsapi/dns_he.sh

@ -134,9 +134,9 @@ _find_zone() {
_zone_ids=$(echo "$_matches" | _egrep_o "hosted_dns_zoneid=[0-9]*&" | cut -d = -f 2 | tr -d '&') _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) _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 if [ -z "$_zone_names" ] || [ -z "$_zone_ids" ]; then
_err "Can not get zone names." _err "Can not get zone names."
return 1 return 1
@ -154,10 +154,14 @@ _find_zone() {
_debug "Looking for zone \"${_attempted_zone}\"" _debug "Looking for zone \"${_attempted_zone}\""
line_num="$(echo "$_zone_names" | grep -n "^$_attempted_zone" | cut -d : -f 1)"
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 if [ "$line_num" ]; then
_zone_id=$(echo "$_zone_ids" | sed -n "${line_num}p") _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
}

20
dnsapi/dns_hostingde.sh

@ -85,6 +85,22 @@ _hostingde_getZoneConfig() {
zoneConfigDnsServerGroupId=$(echo "${curResult}" | _hostingde_parse "dnsServerGroupId") zoneConfigDnsServerGroupId=$(echo "${curResult}" | _hostingde_parse "dnsServerGroupId")
zoneConfigEmailAddress=$(echo "${curResult}" | _hostingde_parse "emailAddress") zoneConfigEmailAddress=$(echo "${curResult}" | _hostingde_parse "emailAddress")
zoneConfigDnsSecMode=$(echo "${curResult}" | _hostingde_parse "dnsSecMode") 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 if [ "${zoneConfigType}" != "\"NATIVE\"" ]; then
_err "Zone is not native" _err "Zone is not native"
returnCode=1 returnCode=1
@ -122,7 +138,7 @@ _hostingde_addRecord() {
_hostingde_getZoneStatus _hostingde_getZoneStatus
_debug "Result of zoneStatus: '${zoneStatus}'" _debug "Result of zoneStatus: '${zoneStatus}'"
done 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}}},\"recordsToAdd\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\",\"ttl\":3600}]}"
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")" curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'" _debug "Result of zoneUpdate: '$curResult'"
@ -146,7 +162,7 @@ _hostingde_removeRecord() {
_hostingde_getZoneStatus _hostingde_getZoneStatus
_debug "Result of zoneStatus: '$zoneStatus'" _debug "Result of zoneStatus: '$zoneStatus'"
done 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}}},\"recordsToDelete\":[{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"\\\"${txtvalue}\\\"\"}]}"
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")" curResult="$(_post "${curData}" "${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate")"
_debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'" _debug "Calling zoneUpdate: '${curData}' '${HOSTINGDE_ENDPOINT}/api/dns/v1/json/zoneUpdate'"
_debug "Result of zoneUpdate: '$curResult'" _debug "Result of zoneUpdate: '$curResult'"

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
}

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

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
}

3
dnsapi/dns_linode_v4.sh

@ -31,7 +31,8 @@ dns_linode_v4_add() {
_payload="{ _payload="{
\"type\": \"TXT\", \"type\": \"TXT\",
\"name\": \"$_sub_domain\", \"name\": \"$_sub_domain\",
\"target\": \"$txtvalue\"
\"target\": \"$txtvalue\",
\"ttl_sec\": 300
}" }"
if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then

60
dnsapi/dns_loopia.sh

@ -4,8 +4,10 @@
#LOOPIA_User="username" #LOOPIA_User="username"
# #
#LOOPIA_Password="password" #LOOPIA_Password="password"
#
#LOOPIA_Api="https://api.loopia.<TLD>/RPCSERV"
LOOPIA_Api="https://api.loopia.se/RPCSERV"
LOOPIA_Api_Default="https://api.loopia.se/RPCSERV"
######## Public functions ##################### ######## Public functions #####################
@ -14,19 +16,11 @@ dns_loopia_add() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
LOOPIA_User=""
LOOPIA_Password=""
_err "You don't specify loopia user and password yet."
_err "Please create you key and try again."
if ! _loopia_load_config; then
return 1 return 1
fi fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable LOOPIA_User "$LOOPIA_User"
_saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
_loopia_save_config
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -47,19 +41,11 @@ dns_loopia_rm() {
fulldomain=$1 fulldomain=$1
txtvalue=$2 txtvalue=$2
LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
LOOPIA_User=""
LOOPIA_Password=""
_err "You don't specify LOOPIA user and password yet."
_err "Please create you key and try again."
if ! _loopia_load_config; then
return 1 return 1
fi fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable LOOPIA_User "$LOOPIA_User"
_saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
_loopia_save_config
_debug "First detect the root zone" _debug "First detect the root zone"
if ! _get_root "$fulldomain"; then if ! _get_root "$fulldomain"; then
@ -84,7 +70,7 @@ dns_loopia_rm() {
<value><string>%s</string></value> <value><string>%s</string></value>
</param> </param>
</params> </params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$_domain" "$_sub_domain")
</methodCall>' "$LOOPIA_User" "$LOOPIA_Password" "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
@ -96,6 +82,36 @@ dns_loopia_rm() {
#################### Private functions below ################################## #################### 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() { _loopia_get_records() {
domain=$1 domain=$1
sub_domain=$2 sub_domain=$2

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
}

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/Neilpang/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
}

2
dnsapi/dns_myapi.sh

@ -11,6 +11,8 @@
# #
######## Public functions ##################### ######## 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" #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_myapi_add() { dns_myapi_add() {
fulldomain=$1 fulldomain=$1

2
dnsapi/dns_namecheap.sh

@ -164,7 +164,7 @@ _namecheap_set_publicip() {
_debug 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}') 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)://.*')
addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https):\/\/.*')
_debug2 ip "$ip" _debug2 ip "$ip"
_debug2 addr "$addr" _debug2 addr "$addr"

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

2
dnsapi/dns_netcup.sh

@ -117,7 +117,7 @@ dns_netcup_rm() {
login() { login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST") tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(_getfield "$tmp" "8" | sed s/\"responsedata\":\{\"apisessionid\":\"//g | sed 's/\"\}\}//g')
sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
_debug "$tmp" _debug "$tmp"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg" _err "$msg"

185
dnsapi/dns_nic.sh

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
#NIC_Token="sdfsdfsdfljlbjkljlkjsdfoiwjedfglgkdlfgkfgldfkg"
#
#NIC_Username="000000/NIC-D"
#NIC_Password="xxxxxxx"
NIC_Api="https://api.nic.ru"
dns_nic_add() {
fulldomain="${1}"
txtvalue="${2}"
NIC_Token="${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}"
NIC_Username="${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}"
NIC_Password="${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}"
if [ -z "$NIC_Token" ] || [ -z "$NIC_Username" ] || [ -z "$NIC_Password" ]; then
NIC_Token=""
NIC_Username=""
NIC_Password=""
_err "You must export variables: NIC_Token, NIC_Username and NIC_Password"
return 1
fi
_saveaccountconf_mutable NIC_Customer "$NIC_Token"
_saveaccountconf_mutable NIC_Username "$NIC_Username"
_saveaccountconf_mutable NIC_Password "$NIC_Password"
if ! _nic_get_authtoken "$NIC_Username" "$NIC_Password" "$NIC_Token"; 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}"
NIC_Token="${NIC_Token:-$(_readaccountconf_mutable NIC_Token)}"
NIC_Username="${NIC_Username:-$(_readaccountconf_mutable NIC_Username)}"
NIC_Password="${NIC_Password:-$(_readaccountconf_mutable NIC_Password)}"
if [ -z "$NIC_Token" ] || [ -z "$NIC_Username" ] || [ -z "$NIC_Password" ]; then
NIC_Token=""
NIC_Username=""
NIC_Password=""
_err "You must export variables: NIC_Token, NIC_Username and NIC_Password"
return 1
fi
if ! _nic_get_authtoken "$NIC_Username" "$NIC_Password" "$NIC_Token"; 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_authtoken() {
username="$1"
password="$2"
token="$3"
_info "Getting NIC auth token"
export _H1="Authorization: Basic $token"
export _H2="Content-Type: application/x-www-form-urlencoded"
res=$(_post "grant_type=password&username=$username&password=$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 "$_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
}

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
}

24
dnsapi/dns_nsupdate.sh

@ -6,14 +6,22 @@
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
# save the dns server and key to the account conf file.
_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}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53 [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
# save the dns server and key to the account conf file.
_saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
_saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
_saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
_saveaccountconf NSUPDATE_ZONE "${NSUPDATE_ZONE}"
_info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d"
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
@ -42,6 +50,12 @@ 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 [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53

179
dnsapi/dns_one.sh

@ -0,0 +1,179 @@
#!/usr/bin/env sh
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
# one.com ui wrapper for acme.sh
# Author: github: @diseq
# Created: 2019-02-17
# Fixed by: @der-berni
# Modified: 2019-05-31
#
# export ONECOM_User="username"
# export ONECOM_Password="password"
#
# Usage:
# acme.sh --issue --dns dns_one -d example.com
#
# only single domain supported atm
dns_one_add() {
fulldomain=$1
txtvalue=$2
if ! _dns_one_login; then
_err "login failed"
return 1
fi
_debug "detect the root domain"
if ! _get_root "$fulldomain"; then
_err "root domain not found"
return 1
fi
mysubdomain=$_sub_domain
mydomain=$_domain
_debug mysubdomain "$mysubdomain"
_debug mydomain "$mydomain"
# get entries
response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
_debug response "$response"
# Update the IP address for domain entry
postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"TXT\",\"prefix\":\"$mysubdomain\",\"content\":\"$txtvalue\"}}"
_debug postdata "$postdata"
response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records" "" "POST" "application/json")"
response="$(echo "$response" | _normalizeJson)"
_debug response "$response"
id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
if [ -z "$id" ]; then
_err "Add txt record error."
return 1
else
_info "Added, OK ($id)"
return 0
fi
}
dns_one_rm() {
fulldomain=$1
txtvalue=$2
if ! _dns_one_login; then
_err "login failed"
return 1
fi
_debug "detect the root domain"
if ! _get_root "$fulldomain"; then
_err "root domain not found"
return 1
fi
mysubdomain=$_sub_domain
mydomain=$_domain
_debug mysubdomain "$mysubdomain"
_debug mydomain "$mydomain"
# get entries
response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
response="$(echo "$response" | _normalizeJson)"
_debug response "$response"
id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}.*/\1/p")
if [ -z "$id" ]; then
_err "Txt record not found."
return 1
fi
# delete entry
response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records/$id" "" "DELETE" "application/json")"
response="$(echo "$response" | _normalizeJson)"
_debug response "$response"
if [ "$response" = '{"result":null,"metadata":null}' ]; then
_info "Removed, OK"
return 0
else
_err "Removing txt record error."
return 1
fi
}
#_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
response="$(_get "https://www.one.com/admin/api/domains/$h/dns/custom_records")"
if ! _contains "$response" "CRMRST_000302"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
_err "Unable to parse this domain"
return 1
}
_dns_one_login() {
# get credentials
ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
ONECOM_User=""
ONECOM_Password=""
_err "You didn't specify a one.com username and password yet."
_err "Please create the key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable ONECOM_User "$ONECOM_User"
_saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
# Login with user and password
postdata="loginDomain=true"
postdata="$postdata&displayUsername=$ONECOM_User"
postdata="$postdata&username=$ONECOM_User"
postdata="$postdata&targetDomain="
postdata="$postdata&password1=$ONECOM_Password"
postdata="$postdata&loginTarget="
#_debug postdata "$postdata"
response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")"
#_debug response "$response"
# Get SessionID
JSESSIONID="$(grep "OneSIDCrmAdmin" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _head_n 1 | _egrep_o 'OneSIDCrmAdmin=[^;]*;' | tr -d ';')"
_debug jsessionid "$JSESSIONID"
if [ -z "$JSESSIONID" ]; then
_err "error sessionid cookie not found"
return 1
fi
export _H1="Cookie: ${JSESSIONID}"
return 0
}

2
dnsapi/dns_ovh.sh

@ -121,7 +121,7 @@ _initAuth() {
_info "Checking authentication" _info "Checking authentication"
if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL"; then
if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL" || _contains "$response" "NOT_CREDENTIAL"; then
_err "The consumer key is invalid: $OVH_CK" _err "The consumer key is invalid: $OVH_CK"
_err "Please retry to create a new one." _err "Please retry to create a new one."
_clearaccountconf OVH_CK _clearaccountconf OVH_CK

414
dnsapi/dns_pleskxml.sh

@ -0,0 +1,414 @@
#!/usr/bin/env sh
## Name: dns_pleskxml.sh
## Created by Stilez.
## Also uses some code from PR#1832 by @romanlum (https://github.com/Neilpang/acme.sh/pull/1832/files)
## This DNS-01 method uses the Plesk XML API described at:
## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709
## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784
## Note: a DNS ID with host = empty string is OK for this API, see
## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action.
## So this API module can handle such a request, if needed.
## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records.
## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user
## before this module is called (case sensitive):
##
## ```
## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php"
## (or probably something similar)
## export pleskxml_user="my plesk username"
## export pleskxml_pass="my plesk password"
## ```
## Ok, let's issue a cert now:
## ```
## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com
## ```
##
## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed.
#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ##################################
pleskxml_init_checks_done=0
# Variable containing bare newline - not a style issue
# shellcheck disable=SC1004
NEWLINE='\
'
pleskxml_tplt_get_domains="<packet><customer><get-domain-list><filter/></get-domain-list></customer></packet>"
# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh
# Also used to test credentials and URI.
# No params.
pleskxml_tplt_get_dns_records="<packet><dns><get_rec><filter><site-id>%s</site-id></filter></get_rec></dns></packet>"
# Get all DNS records for a Plesk domain ID.
# PARAM = Plesk domain id to query
pleskxml_tplt_add_txt_record="<packet><dns><add_rec><site-id>%s</site-id><type>TXT</type><host>%s</host><value>%s</value></add_rec></dns></packet>"
# Add a TXT record to a domain.
# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value
pleskxml_tplt_rmv_dns_record="<packet><dns><del_rec><filter><id>%s</id></filter></del_rec></dns></packet>"
# Delete a specific TXT record from a domain.
# PARAM = the Plesk internal ID for the DNS record to be deleted
#################### Public functions ##################################
#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_pleskxml_add() {
fulldomain=$1
txtvalue=$2
_info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..."
# Get credentials if not already checked, and confirm we can log in to Plesk XML API
if ! _credential_check; then
return 1
fi
# Get root and subdomain details, and Plesk domain ID
if ! _pleskxml_get_root_domain "$fulldomain"; then
return 1
fi
_debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record'
# printf using template in a variable - not a style issue
# shellcheck disable=SC2059
request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")"
if ! _call_api "$request"; then
return 1
fi
# OK, we should have added a TXT record. Let's check and return success if so.
# All that should be left in the result, is one section, containing <result><status>ok</status><id>NEW_DNS_RECORD_ID</id></result>
results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '<status>')"
if ! _value "$results" | grep '<status>ok</status>' | grep '<id>[0-9]\{1,\}</id>' >/dev/null; then
# Error - doesn't contain expected string. Something's wrong.
_err 'Error when calling Plesk XML API.'
_err 'The result did not contain the expected <id>XXXXX</id> section, or contained other values as well.'
_err 'This is unexpected: something has gone wrong.'
_err 'The full response was:'
_err "$pleskxml_prettyprint_result"
return 1
fi
recid="$(_value "$results" | grep '<id>[0-9]\{1,\}</id>' | sed 's/^.*<id>\([0-9]\{1,\}\)<\/id>.*$/\1/')"
_info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()."
return 0
}
#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_pleskxml_rm() {
fulldomain=$1
txtvalue=$2
_info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..."
# Get credentials if not already checked, and confirm we can log in to Plesk XML API
if ! _credential_check; then
return 1
fi
# Get root and subdomain details, and Plesk domain ID
if ! _pleskxml_get_root_domain "$fulldomain"; then
return 1
fi
_debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs'
# printf using template in a variable - not a style issue
# shellcheck disable=SC2059
request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")"
if ! _call_api "$request"; then
return 1
fi
# Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have)
# Also strip out spaces between tags, redundant <data> and </data> group tags and any <self-closing/> tags
reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '<status>ok</status>' \
| sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s#</\{0,1\}data>##g;s#<[a-z][^/<>]*/>##g' \
| grep "<site-id>${root_domain_id}</site-id>" \
| grep '<id>[0-9]\{1,\}</id>' \
| grep '<type>TXT</type>'
)"
if [ -z "$reclist" ]; then
_err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting."
return 1
fi
_debug "Got list of DNS TXT records for root domain '$root_domain_name':"
_debug "$reclist"
recid="$(_value "$reclist" \
| grep "<host>${fulldomain}.</host>" \
| grep "<value>${txtvalue}</value>" \
| sed 's/^.*<id>\([0-9]\{1,\}\)<\/id>.*$/\1/'
)"
if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
_err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
_err "Cannot delete TXT record. Exiting."
return 1
fi
_debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}"
_debug 'Calling Plesk XML API to remove TXT record'
# printf using template in a variable - not a style issue
# shellcheck disable=SC2059
request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")"
if ! _call_api "$request"; then
return 1
fi
# OK, we should have removed a TXT record. Let's check and return success if so.
# All that should be left in the result, is one section, containing <result><status>ok</status><id>PLESK_DELETED_DNS_RECORD_ID</id></result>
results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '<status>')"
if ! _value "$results" | grep '<status>ok</status>' | grep '<id>[0-9]\{1,\}</id>' >/dev/null; then
# Error - doesn't contain expected string. Something's wrong.
_err 'Error when calling Plesk XML API.'
_err 'The result did not contain the expected <id>XXXXX</id> section, or contained other values as well.'
_err 'This is unexpected: something has gone wrong.'
_err 'The full response was:'
_err "$pleskxml_prettyprint_result"
return 1
fi
_info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()."
return 0
}
#################### Private functions below (utility functions) ##################################
# Outputs value of a variable without additional newlines etc
_value() {
printf '%s' "$1"
}
# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between
# $1, $2 = where to cut
# $3 = FQDN
_valuecut() {
printf '%s' "$3" | cut -d . -f "${1}-${2}"
}
# Counts '.' present in a domain name or other string
# $1 = domain name
_countdots() {
_value "$1" | tr -dc '.' | wc -c | sed 's/ //g'
}
# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines
# $1 - result string from API
# $2 - plain text tag to resplit on (usually "result" or "domain"). NOT REGEX
# $3 - basic regex to recognise useful return lines
# note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment.
# Last line could change to <sed -n '/.../p'> instead, with suitable escaping of ['"/$],
# if future Plesk XML API changes ever require extended regex
_api_response_split() {
printf '%s' "$1" \
| sed 's/^ +//;s/ +$//' \
| tr -d '\n\r' \
| sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" \
| grep "$3"
}
#################### Private functions below (DNS functions) ##################################
# Calls Plesk XML API, and checks results for obvious issues
_call_api() {
request="$1"
errtext=''
_debug 'Entered _call_api(). Calling Plesk XML API with request:'
_debug "'$request'"
export _H1="HTTP_AUTH_LOGIN: $pleskxml_user"
export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass"
export _H3="content-Type: text/xml"
export _H4="HTTP_PRETTY_PRINT: true"
pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")"
pleskxml_retcode="$?"
_debug 'The responses from the Plesk XML server were:'
_debug "retcode=$pleskxml_retcode. Literal response:"
_debug "'$pleskxml_prettyprint_result'"
# Detect any <status> that isn't "ok". None of the used calls should fail if the API is working correctly.
# Also detect if there simply aren't any status lines (null result?) and report that, as well.
statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *<status>[^<]*</status> *$')"
statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *<status>ok</status> *$')"
if [ -z "$statuslines_count_total" ]; then
# We have no status lines at all. Results are empty
errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.'
elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then
# We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext"
# Workaround for basic regex:
# - filter output to keep only lines like this: "SPACES<TAG>text</TAG>SPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok)
# - then edit the 3 "useful" error tokens individually and remove closing tags on all lines
# - then filter again to remove all lines not edited (which will be the lines not starting A-Z)
errtext="$(_value "$pleskxml_prettyprint_result" \
| grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' \
| sed 's/^ *<status>/Status: /;s/^ *<errcode>/Error code: /;s/^ *<errtext>/Error text: /;s/<\/.*$//' \
| grep '^[A-Z]'
)"
fi
if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then
# Call failed, for reasons either in the retcode or the response text...
if [ "$pleskxml_retcode" -eq 0 ]; then
_err "The POST request was successfully sent to the Plesk server."
else
_err "The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server)."
fi
if [ "$errtext" != "" ]; then
_err 'The error responses received from the Plesk server were:'
_err "$errtext"
else
_err "No additional error messages were received back from the Plesk server"
fi
_err "The Plesk XML API call failed."
return 1
fi
_debug "Leaving _call_api(). Successful call."
return 0
}
# Startup checks (credentials, URI)
_credential_check() {
_debug "Checking Plesk XML API login credentials and URI..."
if [ "$pleskxml_init_checks_done" -eq 1 ]; then
_debug "Initial checks already done, no need to repeat. Skipped."
return 0
fi
pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}"
pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}"
pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}"
if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then
pleskxml_user=""
pleskxml_pass=""
pleskxml_uri=""
_err "You didn't specify one or more of the Plesk XML API username, password, or URI."
_err "Please create these and try again."
_err "Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation."
return 1
fi
# Test the API is usable, by trying to read the list of managed domains...
_call_api "$pleskxml_tplt_get_domains"
if [ "$pleskxml_retcode" -ne 0 ]; then
_err 'Failed to access Plesk XML API.'
_err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again."
return 1
fi
_saveaccountconf_mutable pleskxml_uri "$pleskxml_uri"
_saveaccountconf_mutable pleskxml_user "$pleskxml_user"
_saveaccountconf_mutable pleskxml_pass "$pleskxml_pass"
_debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use."
pleskxml_init_checks_done=1
return 0
}
# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any.
# IMPORTANT NOTE: a result with host = empty string is OK for this API, see
# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
# See notes at top of this file
_pleskxml_get_root_domain() {
original_full_domain_name="$1"
_debug "Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account."
# test if the domain as provided is valid for splitting.
if [ "$(_countdots "$original_full_domain_name")" -eq 0 ]; then
_err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record."
return 1
fi
_debug "Querying Plesk server for list of managed domains..."
_call_api "$pleskxml_tplt_get_domains"
if [ "$pleskxml_retcode" -ne 0 ]; then
return 1
fi
# Generate a crude list of domains known to this Plesk account.
# We convert <ascii-name> tags to <name> so it'll flag on a hit with either <name> or <ascii-name> fields,
# for non-Western character sets.
# Output will be one line per known domain, containing 2 <name> tages and a single <id> tag
# We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' '<type>domain</type>' | sed 's/<ascii-name>/<name>/g;s/<\/ascii-name>/<\/name>/g' | grep '<name>' | grep '<id>')"
_debug 'Domains managed by Plesk server are (ignore the hacked output):'
_debug "$output"
# loop and test if domain, or any parent domain, is managed by Plesk
# Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain
root_domain_name="$original_full_domain_name"
while true; do
_debug "Checking if '$root_domain_name' is managed by the Plesk server..."
root_domain_id="$(_value "$output" | grep "<name>$root_domain_name</name>" | _head_n 1 | sed 's/^.*<id>\([0-9]\{1,\}\)<\/id>.*$/\1/')"
if [ -n "$root_domain_id" ]; then
# Found a match
# SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT.
# SO WE HANDLE IT AND DON'T PREVENT IT
sub_domain_name="$(_value "$original_full_domain_name" | sed "s/\.\{0,1\}${root_domain_name}"'$//')"
_info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning."
return 0
fi
# No match, try next parent up (if any)...
root_domain_name="$(_valuecut 2 1000 "$root_domain_name")"
if [ "$(_countdots "$root_domain_name")" -eq 0 ]; then
_debug "No match, and next parent would be a TLD..."
_err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk."
_err "Are you sure that this domain is managed by this Plesk server?"
return 1
fi
_debug "No match, trying next parent up..."
done
}

224
dnsapi/dns_rcode0.sh

@ -0,0 +1,224 @@
#!/usr/bin/env sh
#Rcode0 API Integration
#https://my.rcodezero.at/api-doc
#
# log into https://my.rcodezero.at/enableapi and get your ACME API Token (the ACME API token has limited
# access to the REST calls needed for acme.sh only)
#
#RCODE0_URL="https://my.rcodezero.at"
#RCODE0_API_TOKEN="0123456789ABCDEF"
#RCODE0_TTL=60
DEFAULT_RCODE0_URL="https://my.rcodezero.at"
DEFAULT_RCODE0_TTL=60
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
#fulldomain
#txtvalue
dns_rcode0_add() {
fulldomain=$1
txtvalue=$2
RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
if [ -z "$RCODE0_URL" ]; then
RCODE0_URL="$DEFAULT_RCODE0_URL"
fi
if [ -z "$RCODE0_API_TOKEN" ]; then
RCODE0_API_TOKEN=""
_err "Missing Rcode0 ACME API Token."
_err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
return 1
fi
if [ -z "$RCODE0_TTL" ]; then
RCODE0_TTL="$DEFAULT_RCODE0_TTL"
fi
#save the token to the account conf file.
_saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
if [ "$RCODE0_URL" != "$DEFAULT_RCODE0_URL" ]; then
_saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
fi
if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
_saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
fi
_debug "Detect root zone"
if ! _get_root "$fulldomain"; then
_err "No 'MASTER' zone for $fulldomain found at RcodeZero Anycast."
return 1
fi
_debug _domain "$_domain"
_debug "Adding record"
_record_string=""
_build_record_string "$txtvalue"
_list_existingchallenges
for oldchallenge in $_existing_challenges; do
_build_record_string "$oldchallenge"
done
_debug "Challenges: $_existing_challenges"
if [ -z "$_existing_challenges" ]; then
if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"add\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
_err "Add txt record error."
return 1
fi
else
# try update in case a records exists (need for wildcard certs)
if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
_err "Set txt record error."
return 1
fi
fi
return 0
}
#fulldomain txtvalue
dns_rcode0_rm() {
fulldomain=$1
txtvalue=$2
RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
if [ -z "$RCODE0_URL" ]; then
RCODE0_URL="$DEFAULT_RCODE0_URL"
fi
if [ -z "$RCODE0_API_TOKEN" ]; then
RCODE0_API_TOKEN=""
_err "Missing Rcode0 API Token."
_err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
return 1
fi
#save the api addr and key to the account conf file.
_saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
_saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
_saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
fi
if [ -z "$RCODE0_TTL" ]; then
RCODE0_TTL="$DEFAULT_RCODE0_TTL"
fi
_debug "Detect root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug "Remove record"
#Enumerate existing acme challenges
_list_existingchallenges
if _contains "$_existing_challenges" "$txtvalue"; then
#Delete all challenges (PowerDNS API does not allow to delete content)
if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"delete\", \"name\": \"$fulldomain.\", \"type\": \"TXT\"}]"; then
_err "Delete txt record error."
return 1
fi
_record_string=""
#If the only existing challenge was the challenge to delete: nothing to do
if ! [ "$_existing_challenges" = "$txtvalue" ]; then
for oldchallenge in $_existing_challenges; do
#Build up the challenges to re-add, ommitting the one what should be deleted
if ! [ "$oldchallenge" = "$txtvalue" ]; then
_build_record_string "$oldchallenge"
fi
done
#Recreate the existing challenges
if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
_err "Set txt record error."
return 1
fi
fi
else
_info "Record not found, nothing to remove"
fi
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _domain=domain.com
_get_root() {
domain=$1
i=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug "try to find: $h"
if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then
if [ "$response" = "[\"found\"]" ]; then
_domain="$h"
if [ -z "$h" ]; then
_domain="=2E"
fi
return 0
elif [ "$response" = "[\"not a master domain\"]" ]; then
return 1
fi
fi
if [ -z "$h" ]; then
return 1
fi
i=$(_math $i + 1)
done
_debug "no matching domain for $domain found"
return 1
}
_rcode0_rest() {
method=$1
ep=$2
data=$3
export _H1="Authorization: Bearer $RCODE0_API_TOKEN"
if [ ! "$method" = "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$RCODE0_URL$ep" "" "$method")"
else
response="$(_get "$RCODE0_URL$ep")"
fi
if [ "$?" != "0" ]; then
_err "error $ep"
return 1
fi
_debug2 response "$response"
return 0
}
_build_record_string() {
_record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
}
_list_existingchallenges() {
_rcode0_rest "GET" "/api/v1/acme/zones/$_domain/rrsets"
_existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
_debug2 "$_existing_challenges"
}

63
dnsapi/dns_regru.sh

@ -0,0 +1,63 @@
#!/usr/bin/env sh
#
# REGRU_API_Username="test"
#
# REGRU_API_Password="test"
#
_domain=$_domain
REGRU_API_URL="https://api.reg.ru/api/regru2"
######## Public functions #####################
dns_regru_add() {
fulldomain=$1
txtvalue=$2
REGRU_API_Username="${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}"
REGRU_API_Password="${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}"
if [ -z "$REGRU_API_Username" ] || [ -z "$REGRU_API_Password" ]; then
REGRU_API_Username=""
REGRU_API_Password=""
_err "You don't specify regru password or username."
return 1
fi
_saveaccountconf_mutable REGRU_API_Username "$REGRU_API_Username"
_saveaccountconf_mutable REGRU_API_Password "$REGRU_API_Password"
_info "Adding TXT record to ${fulldomain}"
response="$(_get "$REGRU_API_URL/zone/add_txt?input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not create resource record, check logs"
_err "${response}"
return 1
}
dns_regru_rm() {
fulldomain=$1
txtvalue=$2
REGRU_API_Username="${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}"
REGRU_API_Password="${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}"
if [ -z "$REGRU_API_Username" ] || [ -z "$REGRU_API_Password" ]; then
REGRU_API_Username=""
REGRU_API_Password=""
_err "You don't specify regru password or username."
return 1
fi
_info "Deleting resource record $fulldomain"
response="$(_get "$REGRU_API_URL/zone/remove_record?input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json")"
if _contains "${response}" 'success'; then
return 0
fi
_err "Could not delete resource record, check logs"
_err "${response}"
return 1
}

261
dnsapi/dns_schlundtech.sh

@ -0,0 +1,261 @@
#!/usr/bin/env sh
# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
# Schlundtech DNS API
# Author: mod242
# Created: 2019-40-29
# Completly based on the autoDNS xml api wrapper by auerswald@gmail.com
#
# export SCHLUNDTECH_USER="username"
# export SCHLUNDTECH_PASSWORD="password"
#
# Usage:
# acme.sh --issue --dns dns_schlundtech -d example.com
SCHLUNDTECH_API="https://gateway.schlundtech.de"
# Arguments:
# txtdomain
# txt
dns_schlundtech_add() {
fulldomain="$1"
txtvalue="$2"
SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}"
SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}"
if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then
_err "You didn't specify schlundtech user and password."
return 1
fi
_saveaccountconf_mutable SCHLUNDTECH_USER "$SCHLUNDTECH_USER"
_saveaccountconf_mutable SCHLUNDTECH_PASSWORD "$SCHLUNDTECH_PASSWORD"
_debug "First detect the root zone"
if ! _get_autodns_zone "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _zone "$_zone"
_debug _system_ns "$_system_ns"
_info "Adding TXT record"
autodns_response="$(_autodns_zone_update "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")"
if [ "$?" -eq "0" ]; then
_info "Added, OK"
return 0
fi
return 1
}
# Arguments:
# txtdomain
# txt
dns_schlundtech_rm() {
fulldomain="$1"
txtvalue="$2"
SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}"
SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}"
if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then
_err "You didn't specify schlundtech user and password."
return 1
fi
_debug "First detect the root zone"
if ! _get_autodns_zone "$fulldomain"; then
_err "zone not found"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _zone "$_zone"
_debug _system_ns "$_system_ns"
_info "Delete TXT record"
autodns_response="$(_autodns_zone_cleanup "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")"
if [ "$?" -eq "0" ]; then
_info "Deleted, OK"
return 0
fi
return 1
}
#################### Private functions below ##################################
# Arguments:
# fulldomain
# Returns:
# _sub_domain=_acme-challenge.www
# _zone=domain.com
# _system_ns
_get_autodns_zone() {
domain="$1"
i=2
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
if [ -z "$h" ]; then
# not valid
return 1
fi
autodns_response="$(_autodns_zone_inquire "$h")"
if [ "$?" -ne "0" ]; then
_err "invalid domain"
return 1
fi
if _contains "$autodns_response" "<summary>1</summary>" >/dev/null; then
_zone="$(echo "$autodns_response" | _egrep_o '<name>[^<]*</name>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_system_ns="$(echo "$autodns_response" | _egrep_o '<system_ns>[^<]*</system_ns>' | cut -d '>' -f 2 | cut -d '<' -f 1)"
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_build_request_auth_xml() {
printf "<auth>
<user>%s</user>
<password>%s</password>
<context>10</context>
</auth>" "$SCHLUNDTECH_USER" "$SCHLUNDTECH_PASSWORD"
}
# Arguments:
# zone
_build_zone_inquire_xml() {
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<request>
%s
<task>
<code>0205</code>
<view>
<children>1</children>
<limit>1</limit>
</view>
<where>
<key>name</key>
<operator>eq</operator>
<value>%s</value>
</where>
</task>
</request>" "$(_build_request_auth_xml)" "$1"
}
# Arguments:
# zone
# subdomain
# txtvalue
# system_ns
_build_zone_update_xml() {
printf "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<request>
%s
<task>
<code>0202001</code>
<default>
<rr_add>
<name>%s</name>
<ttl>600</ttl>
<type>TXT</type>
<value>%s</value>
</rr_add>
</default>
<zone>
<name>%s</name>
<system_ns>%s</system_ns>
</zone>
</task>
</request>" "$(_build_request_auth_xml)" "$2" "$3" "$1" "$4"
}
# Arguments:
# zone
_autodns_zone_inquire() {
request_data="$(_build_zone_inquire_xml "$1")"
autodns_response="$(_autodns_api_call "$request_data")"
ret="$?"
printf "%s" "$autodns_response"
return "$ret"
}
# Arguments:
# zone
# subdomain
# txtvalue
# system_ns
_autodns_zone_update() {
request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")"
autodns_response="$(_autodns_api_call "$request_data")"
ret="$?"
printf "%s" "$autodns_response"
return "$ret"
}
# Arguments:
# zone
# subdomain
# txtvalue
# system_ns
_autodns_zone_cleanup() {
request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")"
# replace 'rr_add>' with 'rr_rem>' in request_data
request_data="$(printf -- "%s" "$request_data" | sed 's/rr_add>/rr_rem>/g')"
autodns_response="$(_autodns_api_call "$request_data")"
ret="$?"
printf "%s" "$autodns_response"
return "$ret"
}
# Arguments:
# request_data
_autodns_api_call() {
request_data="$1"
_debug request_data "$request_data"
autodns_response="$(_post "$request_data" "$SCHLUNDTECH_API")"
ret="$?"
_debug autodns_response "$autodns_response"
if [ "$ret" -ne "0" ]; then
_err "error"
return 1
fi
if _contains "$autodns_response" "<type>success</type>" >/dev/null; then
_info "success"
printf "%s" "$autodns_response"
return 0
fi
return 1
}

147
dnsapi/dns_variomedia.sh

@ -0,0 +1,147 @@
#!/usr/bin/env sh
#
#VARIOMEDIA_API_TOKEN=000011112222333344445555666677778888
VARIOMEDIA_API="https://api.variomedia.de"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_variomedia_add() {
fulldomain=$1
txtvalue=$2
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
if test -z "$VARIOMEDIA_API_TOKEN"; then
VARIOMEDIA_API_TOKEN=""
_err 'VARIOMEDIA_API_TOKEN was not exported'
return 1
fi
_saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
if ! _variomedia_rest POST "dns-records" "{\"data\": {\"type\": \"dns-record\", \"attributes\": {\"record_type\": \"TXT\", \"name\": \"$_sub_domain\", \"domain\": \"$_domain\", \"data\": \"$txtvalue\", \"ttl\":300}}}"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#fulldomain txtvalue
dns_variomedia_rm() {
fulldomain=$1
txtvalue=$2
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
if test -z "$VARIOMEDIA_API_TOKEN"; then
VARIOMEDIA_API_TOKEN=""
_err 'VARIOMEDIA_API_TOKEN was not exported'
return 1
fi
_saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug 'Getting txt records'
if ! _variomedia_rest GET "dns-records?filter[domain]=$_domain"; then
_err 'Error'
return 1
fi
_record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
else
_info "Empty record id, it seems no such record."
return 0
fi
if ! _variomedia_rest DELETE "/dns-records/$_record_id"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#################### 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
if ! _variomedia_rest GET "domains/$h"; then
return 1
fi
if _startswith "$response" "\{\"data\":"; then
if _contains "$response" "\"id\": \"$h\""; 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
}
_variomedia_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
export _H1="Authorization: token $VARIOMEDIA_API_TOKEN"
export _H2="Content-Type: application/vnd.api+json"
export _H3="Accept: application/vnd.variomedia.v1+json"
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$VARIOMEDIA_API/$ep" "" "$m")"
else
response="$(_get "$VARIOMEDIA_API/$ep")"
fi
if [ "$?" != "0" ]; then
_err "Error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

2
dnsapi/dns_vscale.sh

@ -102,7 +102,7 @@ _get_root() {
return 1 return 1
fi fi
hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
hostedzone="$(echo "$response" | tr "{" "\n" | _egrep_o "\"name\":\s*\"$h\".*}")"
if [ "$hostedzone" ]; then if [ "$hostedzone" ]; then
_domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_domain_id" ]; then if [ "$_domain_id" ]; then

161
dnsapi/dns_vultr.sh

@ -0,0 +1,161 @@
#!/usr/bin/env sh
#
#VULTR_API_KEY=000011112222333344445555666677778888
VULTR_Api="https://api.vultr.com/v1"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_vultr_add() {
fulldomain=$1
txtvalue=$2
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
VULTR_API_KEY="${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}"
if test -z "$VULTR_API_KEY"; then
VULTR_API_KEY=''
_err 'VULTR_API_KEY was not exported'
return 1
fi
_saveaccountconf_mutable VULTR_API_KEY "$VULTR_API_KEY"
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
if ! _vultr_rest POST 'dns/create_record' "domain=$_domain&name=$_sub_domain&data=\"$txtvalue\"&type=TXT"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#fulldomain txtvalue
dns_vultr_rm() {
fulldomain=$1
txtvalue=$2
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
VULTR_API_KEY="${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}"
if test -z "$VULTR_API_KEY"; then
VULTR_API_KEY=""
_err 'VULTR_API_KEY was not exported'
return 1
fi
_saveaccountconf_mutable VULTR_API_KEY "$VULTR_API_KEY"
_debug 'First detect the root zone'
if ! _get_root "$fulldomain"; then
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_debug 'Getting txt records'
_vultr_rest GET "dns/records?domain=$_domain"
if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
_err 'Error'
return 1
fi
_record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
_debug _record_id "$_record_id"
if [ "$_record_id" ]; then
_info "Successfully retrieved the record id for ACME challenge."
else
_info "Empty record id, it seems no such record."
return 0
fi
if ! _vultr_rest POST 'dns/delete_record' "domain=$_domain&RECORDID=$_record_id"; then
_err "$response"
return 1
fi
_debug2 _response "$response"
return 0
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain=$1
i=1
while true; do
_domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$_domain"
if [ -z "$_domain" ]; then
return 1
fi
if ! _vultr_rest GET "dns/list"; then
return 1
fi
if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
return 0
else
_debug "Go to next level of $_domain"
fi
else
_err "$response"
return 1
fi
i=$(_math "$i" + 1)
done
return 1
}
_vultr_rest() {
m=$1
ep="$2"
data="$3"
_debug "$ep"
api_key_trimmed=$(echo $VULTR_API_KEY | tr -d '"')
export _H1="Api-Key: $api_key_trimmed"
export _H2='Content-Type: application/x-www-form-urlencoded'
if [ "$m" != "GET" ]; then
_debug data "$data"
response="$(_post "$data" "$VULTR_Api/$ep" "" "$m")"
else
response="$(_get "$VULTR_Api/$ep")"
fi
if [ "$?" != "0" ]; then
_err "Error $ep"
return 1
fi
_debug2 response "$response"
return 0
}

15
dnsapi/dns_yandex.sh

@ -16,7 +16,7 @@ dns_yandex_add() {
_PDD_credentials || return 1 _PDD_credentials || return 1
export _H1="PddToken: $PDD_Token" export _H1="PddToken: $PDD_Token"
_PDD_get_domain "$fulldomain"
_PDD_get_domain "$fulldomain" || return 1
_debug "Found suitable domain in pdd: $curDomain" _debug "Found suitable domain in pdd: $curDomain"
curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}" curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}"
curUri="https://pddimp.yandex.ru/api2/admin/dns/add" curUri="https://pddimp.yandex.ru/api2/admin/dns/add"
@ -30,16 +30,19 @@ dns_yandex_rm() {
_debug "Calling: dns_yandex_rm() '${fulldomain}'" _debug "Calling: dns_yandex_rm() '${fulldomain}'"
_PDD_credentials || return 1 _PDD_credentials || return 1
export _H1="PddToken: $PDD_Token" export _H1="PddToken: $PDD_Token"
record_id=$(pdd_get_record_id "${fulldomain}")
_debug "Result: $record_id"
_PDD_get_domain "$fulldomain"
_PDD_get_domain "$fulldomain" || return 1
_debug "Found suitable domain in pdd: $curDomain" _debug "Found suitable domain in pdd: $curDomain"
record_id=$(pdd_get_record_id "${fulldomain}")
_debug "Result: $record_id"
for rec_i in $record_id; do
curUri="https://pddimp.yandex.ru/api2/admin/dns/del" curUri="https://pddimp.yandex.ru/api2/admin/dns/del"
curData="domain=${curDomain}&record_id=${record_id}"
curData="domain=${curDomain}&record_id=${rec_i}"
curResult="$(_post "${curData}" "${curUri}")" curResult="$(_post "${curData}" "${curUri}")"
_debug "Result: $curResult" _debug "Result: $curResult"
done
} }
#################### Private functions below ################################## #################### Private functions below ##################################
@ -54,7 +57,7 @@ _PDD_get_domain() {
_debug2 "res1" "$res1" _debug2 "res1" "$res1"
__found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')" __found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')"
_debug "found: $__found results on page" _debug "found: $__found results on page"
if [ "$__found" -lt 20 ]; then
if [ "0$__found" -lt 20 ]; then
_debug "last page: $__page" _debug "last page: $__page"
__last=1 __last=1
fi fi

68
notify/dingtalk.sh

@ -0,0 +1,68 @@
#!/usr/bin/env sh
#Support dingtalk webhooks api
#DINGTALK_WEBHOOK="xxxx"
#optional
#DINGTALK_KEYWORD="yyyy"
#DINGTALK_SIGNING_KEY="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx"
# subject content statusCode
dingtalk_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
DINGTALK_WEBHOOK="${DINGTALK_WEBHOOK:-$(_readaccountconf_mutable DINGTALK_WEBHOOK)}"
if [ -z "$DINGTALK_WEBHOOK" ]; then
DINGTALK_WEBHOOK=""
_err "You didn't specify a dingtalk webhooks DINGTALK_WEBHOOK yet."
_err "You can get yours from https://dingtalk.com"
return 1
fi
_saveaccountconf_mutable DINGTALK_WEBHOOK "$DINGTALK_WEBHOOK"
DINGTALK_KEYWORD="${DINGTALK_KEYWORD:-$(_readaccountconf_mutable DINGTALK_KEYWORD)}"
if [ "$DINGTALK_KEYWORD" ]; then
_saveaccountconf_mutable DINGTALK_KEYWORD "$DINGTALK_KEYWORD"
fi
# DINGTALK_SIGNING_KEY="${DINGTALK_SIGNING_KEY:-$(_readaccountconf_mutable DINGTALK_SIGNING_KEY)}"
# if [ -z "$DINGTALK_SIGNING_KEY" ]; then
# DINGTALK_SIGNING_KEY="value1"
# _info "The DINGTALK_SIGNING_KEY is not set, so use the default value1 as key."
# elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$DINGTALK_SIGNING_KEY"; then
# _err "The DINGTALK_SIGNING_KEY \"$DINGTALK_SIGNING_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
# DINGTALK_SIGNING_KEY=""
# return 1
# else
# _saveaccountconf_mutable DINGTALK_SIGNING_KEY "$DINGTALK_SIGNING_KEY"
# fi
# if [ "$DINGTALK_SIGNING_KEY" = "$IFTTT_CONTENT_KEY" ]; then
# DINGTALK_SIGNING_KEY=""
# IFTTT_CONTENT_KEY=""
# _err "The DINGTALK_SIGNING_KEY must not be same as IFTTT_CONTENT_KEY."
# return 1
# fi
_content=$(echo "$_content" | _json_encode)
_subject=$(echo "$_subject" | _json_encode)
_data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$DINGTALK_KEYWORD]\n$_subject\n$_content\"}}"
response="$(_post "$_data" "$DINGTALK_WEBHOOK" "" "POST" "application/json")"
if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then
_info "dingtalk webhooks event fired success."
return 0
fi
_err "dingtalk webhooks event fired error."
_err "$response"
return 1
}

86
notify/ifttt.sh

@ -0,0 +1,86 @@
#!/usr/bin/env sh
#Support ifttt.com webhooks api
#IFTTT_API_KEY="xxxx"
#IFTTT_EVENT_NAME="yyyy"
#IFTTT_SUBJECT_KEY="value1|value2|value3" #optional, use "value1" as default
#IFTTT_CONTENT_KEY="value1|value2|value3" #optional, use "value2" as default
_IFTTT_AVAIL_MSG_KEYS="value1,value2,value3"
# subject content statusCode
ifttt_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
IFTTT_API_KEY="${IFTTT_API_KEY:-$(_readaccountconf_mutable IFTTT_API_KEY)}"
if [ -z "$IFTTT_API_KEY" ]; then
IFTTT_API_KEY=""
_err "You didn't specify a ifttt webhooks api key IFTTT_API_KEY yet."
_err "You can get yours from https://ifttt.com"
return 1
fi
_saveaccountconf_mutable IFTTT_API_KEY "$IFTTT_API_KEY"
IFTTT_EVENT_NAME="${IFTTT_EVENT_NAME:-$(_readaccountconf_mutable IFTTT_EVENT_NAME)}"
if [ -z "$IFTTT_EVENT_NAME" ]; then
IFTTT_EVENT_NAME=""
_err "You didn't specify a ifttt webhooks event name IFTTT_EVENT_NAME yet."
return 1
fi
_saveaccountconf_mutable IFTTT_EVENT_NAME "$IFTTT_EVENT_NAME"
IFTTT_SUBJECT_KEY="${IFTTT_SUBJECT_KEY:-$(_readaccountconf_mutable IFTTT_SUBJECT_KEY)}"
if [ -z "$IFTTT_SUBJECT_KEY" ]; then
IFTTT_SUBJECT_KEY="value1"
_info "The IFTTT_SUBJECT_KEY is not set, so use the default value1 as key."
elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$IFTTT_SUBJECT_KEY"; then
_err "The IFTTT_SUBJECT_KEY \"$IFTTT_SUBJECT_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
IFTTT_SUBJECT_KEY=""
return 1
else
_saveaccountconf_mutable IFTTT_SUBJECT_KEY "$IFTTT_SUBJECT_KEY"
fi
IFTTT_CONTENT_KEY="${IFTTT_CONTENT_KEY:-$(_readaccountconf_mutable IFTTT_CONTENT_KEY)}"
if [ -z "$IFTTT_CONTENT_KEY" ]; then
IFTTT_CONTENT_KEY="value2"
_info "The IFTTT_CONTENT_KEY is not set, so use the default value2 as key."
elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$IFTTT_CONTENT_KEY"; then
_err "The IFTTT_CONTENT_KEY \"$IFTTT_CONTENT_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
IFTTT_CONTENT_KEY=""
return 1
else
_saveaccountconf_mutable IFTTT_CONTENT_KEY "$IFTTT_CONTENT_KEY"
fi
if [ "$IFTTT_SUBJECT_KEY" = "$IFTTT_CONTENT_KEY" ]; then
IFTTT_SUBJECT_KEY=""
IFTTT_CONTENT_KEY=""
_err "The IFTTT_SUBJECT_KEY must not be same as IFTTT_CONTENT_KEY."
return 1
fi
IFTTT_API_URL="https://maker.ifttt.com/trigger/$IFTTT_EVENT_NAME/with/key/$IFTTT_API_KEY"
_content=$(echo "$_content" | _json_encode)
_subject=$(echo "$_subject" | _json_encode)
_data="{\"$IFTTT_SUBJECT_KEY\": \"$_subject\", \"$IFTTT_CONTENT_KEY\": \"$_content\"}"
response="$(_post "$_data" "$IFTTT_API_URL" "" "POST" "application/json")"
if [ "$?" = "0" ] && _contains "$response" "Congratulations"; then
_info "IFTTT webhooks event fired success."
return 0
fi
_err "IFTTT webhooks event fired error."
_err "$response"
return 1
}

138
notify/mail.sh

@ -0,0 +1,138 @@
#!/usr/bin/env sh
#Support local mail app
#MAIL_BIN="sendmail"
#MAIL_FROM="yyyy@gmail.com"
#MAIL_TO="yyyy@gmail.com"
#MAIL_NOVALIDATE=""
mail_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
MAIL_NOVALIDATE="${MAIL_NOVALIDATE:-$(_readaccountconf_mutable MAIL_NOVALIDATE)}"
if [ -n "$MAIL_NOVALIDATE" ]; then
_saveaccountconf_mutable MAIL_NOVALIDATE 1
else
_clearaccountconf "MAIL_NOVALIDATE"
fi
MAIL_BIN="${MAIL_BIN:-$(_readaccountconf_mutable MAIL_BIN)}"
if [ -n "$MAIL_BIN" ] && ! _exists "$MAIL_BIN"; then
_err "It seems that the command $MAIL_BIN is not in path."
return 1
fi
_MAIL_BIN=$(_mail_bin)
if [ -n "$MAIL_BIN" ]; then
_saveaccountconf_mutable MAIL_BIN "$MAIL_BIN"
else
_clearaccountconf "MAIL_BIN"
fi
MAIL_FROM="${MAIL_FROM:-$(_readaccountconf_mutable MAIL_FROM)}"
if [ -n "$MAIL_FROM" ]; then
if ! _mail_valid "$MAIL_FROM"; then
_err "It seems that the MAIL_FROM=$MAIL_FROM is not a valid email address."
return 1
fi
_saveaccountconf_mutable MAIL_FROM "$MAIL_FROM"
fi
MAIL_TO="${MAIL_TO:-$(_readaccountconf_mutable MAIL_TO)}"
if [ -n "$MAIL_TO" ]; then
if ! _mail_valid "$MAIL_TO"; then
_err "It seems that the MAIL_TO=$MAIL_TO is not a valid email address."
return 1
fi
_saveaccountconf_mutable MAIL_TO "$MAIL_TO"
else
MAIL_TO="$(_readaccountconf ACCOUNT_EMAIL)"
if [ -z "$MAIL_TO" ]; then
_err "It seems that account email is empty."
return 1
fi
fi
contenttype="text/plain; charset=utf-8"
subject="=?UTF-8?B?$(echo "$_subject" | _base64)?="
result=$({ _mail_body | eval "$(_mail_cmnd)"; } 2>&1)
# shellcheck disable=SC2181
if [ $? -ne 0 ]; then
_debug "mail send error."
_err "$result"
return 1
fi
_debug "mail send success."
return 0
}
_mail_bin() {
if [ -n "$MAIL_BIN" ]; then
_MAIL_BIN="$MAIL_BIN"
elif _exists "sendmail"; then
_MAIL_BIN="sendmail"
elif _exists "ssmtp"; then
_MAIL_BIN="ssmtp"
elif _exists "mutt"; then
_MAIL_BIN="mutt"
elif _exists "mail"; then
_MAIL_BIN="mail"
else
_err "Please install sendmail, ssmtp, mutt or mail first."
return 1
fi
echo "$_MAIL_BIN"
}
_mail_cmnd() {
case $(basename "$_MAIL_BIN") in
sendmail)
if [ -n "$MAIL_FROM" ]; then
echo "'$_MAIL_BIN' -f '$MAIL_FROM' '$MAIL_TO'"
else
echo "'$_MAIL_BIN' '$MAIL_TO'"
fi
;;
ssmtp)
echo "'$_MAIL_BIN' '$MAIL_TO'"
;;
mutt | mail)
echo "'$_MAIL_BIN' -s '$_subject' '$MAIL_TO'"
;;
*)
_err "Command $MAIL_BIN is not supported, use sendmail, ssmtp, mutt or mail."
return 1
;;
esac
}
_mail_body() {
case $(basename "$_MAIL_BIN") in
sendmail | ssmtp)
if [ -n "$MAIL_FROM" ]; then
echo "From: $MAIL_FROM"
fi
echo "To: $MAIL_TO"
echo "Subject: $subject"
echo "Content-Type: $contenttype"
echo
;;
esac
echo "$_content"
}
_mail_valid() {
[ -n "$MAIL_NOVALIDATE" ] || _contains "$1" "@"
}

131
notify/mailgun.sh

@ -0,0 +1,131 @@
#!/usr/bin/env sh
#Support mailgun.com api
#MAILGUN_API_KEY="xxxx"
#MAILGUN_TO="yyyy@gmail.com"
#MAILGUN_REGION="us|eu" #optional, use "us" as default
#MAILGUN_API_DOMAIN="xxxxxx.com" #optional, use the default sandbox domain
#MAILGUN_FROM="xxx@xxxxx.com" #optional, use the default sandbox account
_MAILGUN_BASE_US="https://api.mailgun.net/v3"
_MAILGUN_BASE_EU="https://api.eu.mailgun.net/v3"
_MAILGUN_BASE="$_MAILGUN_BASE_US"
# subject content statusCode
mailgun_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
MAILGUN_API_KEY="${MAILGUN_API_KEY:-$(_readaccountconf_mutable MAILGUN_API_KEY)}"
if [ -z "$MAILGUN_API_KEY" ]; then
MAILGUN_API_KEY=""
_err "You didn't specify a mailgun api key MAILGUN_API_KEY yet ."
_err "You can get yours from here https://mailgun.com"
return 1
fi
_saveaccountconf_mutable MAILGUN_API_KEY "$MAILGUN_API_KEY"
MAILGUN_REGION="${MAILGUN_REGION:-$(_readaccountconf_mutable MAILGUN_REGION)}"
if [ -z "$MAILGUN_REGION" ]; then
MAILGUN_REGION=""
_debug "The MAILGUN_REGION is not set, so use the default us region."
_MAILGUN_BASE="$_MAILGUN_BASE_US"
else
MAILGUN_REGION="$(echo "$MAILGUN_REGION" | _lower_case)"
_saveaccountconf_mutable MAILGUN_REGION "$MAILGUN_REGION"
if [ "$MAILGUN_REGION" = "us" ]; then
_MAILGUN_BASE="$_MAILGUN_BASE_US"
else
_MAILGUN_BASE="$_MAILGUN_BASE_EU"
fi
fi
_debug _MAILGUN_BASE "$_MAILGUN_BASE"
MAILGUN_TO="${MAILGUN_TO:-$(_readaccountconf_mutable MAILGUN_TO)}"
if [ -z "$MAILGUN_TO" ]; then
MAILGUN_TO=""
_err "You didn't specify an email to MAILGUN_TO receive messages."
return 1
fi
_saveaccountconf_mutable MAILGUN_TO "$MAILGUN_TO"
MAILGUN_API_DOMAIN="${MAILGUN_API_DOMAIN:-$(_readaccountconf_mutable MAILGUN_API_DOMAIN)}"
if [ -z "$MAILGUN_API_DOMAIN" ]; then
_info "The MAILGUN_API_DOMAIN is not set, try to get the default sending sandbox domain for you."
if ! _mailgun_rest GET "/domains"; then
_err "Can not get sandbox domain."
return 1
fi
_sendboxDomain="$(echo "$response" | _egrep_o '"name": *"sandbox.*.mailgun.org"' | cut -d : -f 2 | tr -d '" ')"
_debug _sendboxDomain "$_sendboxDomain"
MAILGUN_API_DOMAIN="$_sendboxDomain"
if [ -z "$MAILGUN_API_DOMAIN" ]; then
_err "Can not get sandbox domain for MAILGUN_API_DOMAIN"
return 1
fi
_info "$(__green "When using sandbox domain, you must verify your email first.")"
#todo: add recepient
fi
if [ -z "$MAILGUN_API_DOMAIN" ]; then
_err "Can not get MAILGUN_API_DOMAIN"
return 1
fi
_saveaccountconf_mutable MAILGUN_API_DOMAIN "$MAILGUN_API_DOMAIN"
MAILGUN_FROM="${MAILGUN_FROM:-$(_readaccountconf_mutable MAILGUN_FROM)}"
if [ -z "$MAILGUN_FROM" ]; then
MAILGUN_FROM="$PROJECT_NAME@$MAILGUN_API_DOMAIN"
_info "The MAILGUN_FROM is not set, so use the default value: $MAILGUN_FROM"
else
_debug MAILGUN_FROM "$MAILGUN_FROM"
_saveaccountconf_mutable MAILGUN_FROM "$MAILGUN_FROM"
fi
#send from url
_msg="/$MAILGUN_API_DOMAIN/messages?from=$(printf "%s" "$MAILGUN_FROM" | _url_encode)&to=$(printf "%s" "$MAILGUN_TO" | _url_encode)&subject=$(printf "%s" "$_subject" | _url_encode)&text=$(printf "%s" "$_content" | _url_encode)"
_debug "_msg" "$_msg"
_mailgun_rest POST "$_msg"
if _contains "$response" "Queued. Thank you."; then
_debug "mailgun send success."
return 0
else
_err "mailgun send error"
_err "$response"
return 1
fi
}
# method uri data
_mailgun_rest() {
_method="$1"
_mguri="$2"
_mgdata="$3"
_debug _mguri "$_mguri"
_mgurl="$_MAILGUN_BASE$_mguri"
_debug _mgurl "$_mgurl"
_auth="$(printf "%s" "api:$MAILGUN_API_KEY" | _base64)"
export _H1="Authorization: Basic $_auth"
export _H2="Content-Type: application/json"
if [ "$_method" = "GET" ]; then
response="$(_get "$_mgurl")"
else
_debug _mgdata "$_mgdata"
response="$(_post "$_mgdata" "$_mgurl" "" "$_method")"
fi
if [ "$?" != "0" ]; then
_err "Error: $_mguri"
_err "$response"
return 1
fi
_debug2 response "$response"
return 0
}

15
notify/pop.sh

@ -0,0 +1,15 @@
#!/usr/bin/env sh
# support pop
pop_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
_err "Not implemented yet."
return 1
}

58
notify/postmark.sh

@ -0,0 +1,58 @@
#!/usr/bin/env sh
#Support postmarkapp.com API (https://postmarkapp.com/developer/user-guide/sending-email/sending-with-api)
#POSTMARK_TOKEN=""
#POSTMARK_TO="xxxx@xxx.com"
#POSTMARK_FROM="xxxx@cccc.com"
postmark_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
POSTMARK_TOKEN="${POSTMARK_TOKEN:-$(_readaccountconf_mutable POSTMARK_TOKEN)}"
if [ -z "$POSTMARK_TOKEN" ]; then
POSTMARK_TOKEN=""
_err "You didn't specify a POSTMARK api token POSTMARK_TOKEN yet ."
_err "You can get yours from here https://account.postmarkapp.com"
return 1
fi
_saveaccountconf_mutable POSTMARK_TOKEN "$POSTMARK_TOKEN"
POSTMARK_TO="${POSTMARK_TO:-$(_readaccountconf_mutable POSTMARK_TO)}"
if [ -z "$POSTMARK_TO" ]; then
POSTMARK_TO=""
_err "You didn't specify an email to POSTMARK_TO receive messages."
return 1
fi
_saveaccountconf_mutable POSTMARK_TO "$POSTMARK_TO"
POSTMARK_FROM="${POSTMARK_FROM:-$(_readaccountconf_mutable POSTMARK_FROM)}"
if [ -z "$POSTMARK_FROM" ]; then
POSTMARK_FROM=""
_err "You didn't specify an email from POSTMARK_FROM receive messages."
return 1
fi
_saveaccountconf_mutable POSTMARK_FROM "$POSTMARK_FROM"
export _H1="Accept: application/json"
export _H2="Content-Type: application/json"
export _H3="X-Postmark-Server-Token: $POSTMARK_TOKEN"
_content="$(echo "$_content" | _json_encode)"
_data="{\"To\": \"$POSTMARK_TO\", \"From\": \"$POSTMARK_FROM\", \"Subject\": \"$_subject\", \"TextBody\": \"$_content\"}"
if _post "$_data" "https://api.postmarkapp.com/email"; then
# shellcheck disable=SC2154
_message=$(printf "%s\n" "$response" | _lower_case | _egrep_o "\"message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
if [ "$_message" = "ok" ]; then
_info "postmark send success."
return 0
fi
fi
_err "postmark send error."
_err "$response"
return 1
}

63
notify/pushover.sh

@ -0,0 +1,63 @@
#!/usr/bin/env sh
#Support for pushover.net's api. Push notification platform for multiple platforms
#PUSHOVER_TOKEN="" Required, pushover application token
#PUSHOVER_USER="" Required, pushover userkey
#PUSHOVER_DEVICE="" Optional, Specific device or devices by hostnames, joining multiples with a comma (such as device=iphone,nexus5)
#PUSHOVER_PRIORITY="" Optional, Lowest Priority (-2), Low Priority (-1), Normal Priority (0), High Priority (1)
PUSHOVER_URI="https://api.pushover.net/1/messages.json"
pushover_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
PUSHOVER_TOKEN="${PUSHOVER_TOKEN:-$(_readaccountconf_mutable PUSHOVER_TOKEN)}"
if [ -z "$PUSHOVER_TOKEN" ]; then
PUSHOVER_TOKEN=""
_err "You didn't specify a PushOver application token yet."
return 1
fi
_saveaccountconf_mutable PUSHOVER_TOKEN "$PUSHOVER_TOKEN"
PUSHOVER_USER="${PUSHOVER_USER:-$(_readaccountconf_mutable PUSHOVER_USER)}"
if [ -z "$PUSHOVER_USER" ]; then
PUSHOVER_USER=""
_err "You didn't specify a PushOver UserKey yet."
return 1
fi
_saveaccountconf_mutable PUSHOVER_USER "$PUSHOVER_USER"
PUSHOVER_DEVICE="${PUSHOVER_DEVICE:-$(_readaccountconf_mutable PUSHOVER_DEVICE)}"
if [ "$PUSHOVER_DEVICE" ]; then
_saveaccountconf_mutable PUSHOVER_DEVICE "$PUSHOVER_DEVICE"
fi
PUSHOVER_PRIORITY="${PUSHOVER_PRIORITY:-$(_readaccountconf_mutable PUSHOVER_PRIORITY)}"
if [ "$PUSHOVER_PRIORITY" ]; then
_saveaccountconf_mutable PUSHOVER_PRIORITY "$PUSHOVER_PRIORITY"
fi
PUSHOVER_SOUND="${PUSHOVER_SOUND:-$(_readaccountconf_mutable PUSHOVER_SOUND)}"
if [ "$PUSHOVER_SOUND" ]; then
_saveaccountconf_mutable PUSHOVER_SOUND "$PUSHOVER_SOUND"
fi
export _H1="Content-Type: application/json"
_content="$(printf "*%s*\n" "$_content" | _json_encode)"
_subject="$(printf "*%s*\n" "$_subject" | _json_encode)"
_data="{\"token\": \"$PUSHOVER_TOKEN\",\"user\": \"$PUSHOVER_USER\",\"title\": \"$_subject\",\"message\": \"$_content\",\"sound\": \"$PUSHOVER_SOUND\", \"device\": \"$PUSHOVER_DEVICE\", \"priority\": \"$PUSHOVER_PRIORITY\"}"
response="$(_post "$_data" "$PUSHOVER_URI")"
if [ "$?" = "0" ] && _contains "$response" "{\"status\":1"; then
_info "PUSHOVER send success."
return 0
fi
_err "PUSHOVER send error."
_err "$response"
return 1
}

56
notify/sendgrid.sh

@ -0,0 +1,56 @@
#!/usr/bin/env sh
#Support SENDGRID.com api
#SENDGRID_API_KEY=""
#SENDGRID_TO="xxxx@xxx.com"
#SENDGRID_FROM="xxxx@cccc.com"
sendgrid_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
SENDGRID_API_KEY="${SENDGRID_API_KEY:-$(_readaccountconf_mutable SENDGRID_API_KEY)}"
if [ -z "$SENDGRID_API_KEY" ]; then
SENDGRID_API_KEY=""
_err "You didn't specify a sendgrid api key SENDGRID_API_KEY yet ."
_err "You can get yours from here https://sendgrid.com"
return 1
fi
_saveaccountconf_mutable SENDGRID_API_KEY "$SENDGRID_API_KEY"
SENDGRID_TO="${SENDGRID_TO:-$(_readaccountconf_mutable SENDGRID_TO)}"
if [ -z "$SENDGRID_TO" ]; then
SENDGRID_TO=""
_err "You didn't specify an email to SENDGRID_TO receive messages."
return 1
fi
_saveaccountconf_mutable SENDGRID_TO "$SENDGRID_TO"
SENDGRID_FROM="${SENDGRID_FROM:-$(_readaccountconf_mutable SENDGRID_FROM)}"
if [ -z "$SENDGRID_FROM" ]; then
SENDGRID_FROM=""
_err "You didn't specify an email to SENDGRID_FROM receive messages."
return 1
fi
_saveaccountconf_mutable SENDGRID_FROM "$SENDGRID_FROM"
export _H1="Authorization: Bearer $SENDGRID_API_KEY"
export _H2="Content-Type: application/json"
_content="$(echo "$_content" | _json_encode)"
_data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
response="$(_post "$_data" "https://api.sendgrid.com/v3/mail/send")"
if [ "$?" = "0" ] && [ -z "$response" ]; then
_info "sendgrid send sccess."
return 0
fi
_err "sendgrid send error."
_err "$response"
return 1
}

55
notify/slack.sh

@ -0,0 +1,55 @@
#!/usr/bin/env sh
#Support Slack webhooks
#SLACK_WEBHOOK_URL=""
#SLACK_CHANNEL=""
#SLACK_USERNAME=""
slack_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_statusCode" "$_statusCode"
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-$(_readaccountconf_mutable SLACK_WEBHOOK_URL)}"
if [ -z "$SLACK_WEBHOOK_URL" ]; then
SLACK_WEBHOOK_URL=""
_err "You didn't specify a Slack webhook url SLACK_WEBHOOK_URL yet."
return 1
fi
_saveaccountconf_mutable SLACK_WEBHOOK_URL "$SLACK_WEBHOOK_URL"
SLACK_CHANNEL="${SLACK_CHANNEL:-$(_readaccountconf_mutable SLACK_CHANNEL)}"
if [ -n "$SLACK_CHANNEL" ]; then
_saveaccountconf_mutable SLACK_CHANNEL "$SLACK_CHANNEL"
fi
SLACK_USERNAME="${SLACK_USERNAME:-$(_readaccountconf_mutable SLACK_USERNAME)}"
if [ -n "$SLACK_USERNAME" ]; then
_saveaccountconf_mutable SLACK_USERNAME "$SLACK_USERNAME"
fi
export _H1="Content-Type: application/json"
_content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)"
_data="{\"text\": \"$_content\", "
if [ -n "$SLACK_CHANNEL" ]; then
_data="$_data\"channel\": \"$SLACK_CHANNEL\", "
fi
if [ -n "$SLACK_USERNAME" ]; then
_data="$_data\"username\": \"$SLACK_USERNAME\", "
fi
_data="$_data\"mrkdwn\": \"true\"}"
if _post "$_data" "$SLACK_WEBHOOK_URL"; then
# shellcheck disable=SC2154
if [ "$response" = "ok" ]; then
_info "slack send success."
return 0
fi
fi
_err "slack send error."
_err "$response"
return 1
}

15
notify/smtp.sh

@ -0,0 +1,15 @@
#!/usr/bin/env sh
# support smtp
smtp_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
_err "Not implemented yet."
return 1
}

90
notify/xmpp.sh

@ -0,0 +1,90 @@
#!/usr/bin/env sh
#Support xmpp via sendxmpp
#XMPP_BIN="/usr/bin/sendxmpp"
#XMPP_BIN_ARGS="-n -t --tls-ca-path=/etc/ssl/certs"
#XMPP_TO="zzzz@example.com"
xmpp_send() {
_subject="$1"
_content="$2"
_statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
_debug "_subject" "$_subject"
_debug "_content" "$_content"
_debug "_statusCode" "$_statusCode"
XMPP_BIN="${XMPP_BIN:-$(_readaccountconf_mutable XMPP_BIN)}"
if [ -n "$XMPP_BIN" ] && ! _exists "$XMPP_BIN"; then
_err "It seems that the command $XMPP_BIN is not in path."
return 1
fi
_XMPP_BIN=$(_xmpp_bin)
if [ -n "$XMPP_BIN" ]; then
_saveaccountconf_mutable XMPP_BIN "$XMPP_BIN"
else
_clearaccountconf "XMPP_BIN"
fi
XMPP_BIN_ARGS="${XMPP_BIN_ARGS:-$(_readaccountconf_mutable XMPP_BIN_ARGS)}"
if [ -n "$XMPP_BIN_ARGS" ]; then
_saveaccountconf_mutable XMPP_BIN_ARGS "$XMPP_BIN_ARGS"
else
_clearaccountconf "XMPP_BIN_ARGS"
fi
XMPP_TO="${XMPP_TO:-$(_readaccountconf_mutable XMPP_TO)}"
if [ -n "$XMPP_TO" ]; then
if ! _xmpp_valid "$XMPP_TO"; then
_err "It seems that the XMPP_TO=$XMPP_TO is not a valid xmpp address."
return 1
fi
_saveaccountconf_mutable XMPP_TO "$XMPP_TO"
fi
result=$({ _xmpp_message | eval "$(_xmpp_cmnd)"; } 2>&1)
# shellcheck disable=SC2181
if [ $? -ne 0 ]; then
_debug "xmpp send error."
_err "$result"
return 1
fi
_debug "xmpp send success."
return 0
}
_xmpp_bin() {
if [ -n "$XMPP_BIN" ]; then
_XMPP_BIN="$XMPP_BIN"
elif _exists "sendxmpp"; then
_XMPP_BIN="sendxmpp"
else
_err "Please install sendxmpp first."
return 1
fi
echo "$_XMPP_BIN"
}
_xmpp_cmnd() {
case $(basename "$_XMPP_BIN") in
sendxmpp)
echo "'$_XMPP_BIN' '$XMPP_TO' $XMPP_BIN_ARGS"
;;
*)
_err "Command $XMPP_BIN is not supported, use sendxmpp."
return 1
;;
esac
}
_xmpp_message() {
echo "$_subject"
}
_xmpp_valid() {
_contains "$1" "@"
}
Loading…
Cancel
Save