]>
Commit | Line | Data |
---|---|---|
1 | #!/usr/bin/env sh | |
2 | ||
3 | #This file name is "dns_dnsservices.sh" | |
4 | #Script for Danish DNS registra and DNS hosting provider https://dns.services | |
5 | # | |
6 | #Author: Bjarke Bruun <bbruun@gmail.com> | |
7 | #Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152 | |
8 | ||
9 | # Global variable to connect to the DNS Services API | |
10 | DNSServices_API=https://dns.services/api | |
11 | ||
12 | ######## Public functions ##################### | |
13 | ||
14 | #Usage: dns_dnsservices_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" | |
15 | dns_dnsservices_add() { | |
16 | fulldomain=$1 | |
17 | txtvalue=$2 | |
18 | ||
19 | _info "Using dns.services to create ACME DNS challenge" | |
20 | _debug2 add_fulldomain "$fulldomain" | |
21 | _debug2 add_txtvalue "$txtvalue" | |
22 | ||
23 | # Read username/password from environment or .acme.sh/accounts.conf | |
24 | DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" | |
25 | DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" | |
26 | if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then | |
27 | DnsServices_Username="" | |
28 | DnsServices_Password="" | |
29 | _err "You didn't specify dns.services api username and password yet." | |
30 | _err "Set environment variables DnsServices_Username and DnsServices_Password" | |
31 | return 1 | |
32 | fi | |
33 | ||
34 | # Setup GET/POST/DELETE headers | |
35 | _setup_headers | |
36 | ||
37 | #save the credentials to the account conf file. | |
38 | _saveaccountconf_mutable DnsServices_Username "$DnsServices_Username" | |
39 | _saveaccountconf_mutable DnsServices_Password "$DnsServices_Password" | |
40 | ||
41 | if ! _contains "$DnsServices_Username" "@"; then | |
42 | _err "It seems that the username variable DnsServices_Username has not been set/left blank" | |
43 | _err "or is not a valid email. Please correct and try again." | |
44 | return 1 | |
45 | fi | |
46 | ||
47 | if ! _get_root "${fulldomain}"; then | |
48 | _err "Invalid domain ${fulldomain}" | |
49 | return 1 | |
50 | fi | |
51 | ||
52 | if ! createRecord "$fulldomain" "${txtvalue}"; then | |
53 | _err "Error creating TXT record in domain $fulldomain in $rootZoneName" | |
54 | return 1 | |
55 | fi | |
56 | ||
57 | _debug2 challenge-created "Created $fulldomain" | |
58 | return 0 | |
59 | } | |
60 | ||
61 | #Usage: fulldomain txtvalue | |
62 | #Description: Remove the txt record after validation. | |
63 | dns_dnsservices_rm() { | |
64 | fulldomain=$1 | |
65 | txtvalue=$2 | |
66 | ||
67 | _info "Using dns.services to delete challenge $fulldomain TXT $txtvalue" | |
68 | _debug rm_fulldomain "$fulldomain" | |
69 | _debug rm_txtvalue "$txtvalue" | |
70 | ||
71 | # Read username/password from environment or .acme.sh/accounts.conf | |
72 | DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" | |
73 | DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" | |
74 | if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then | |
75 | DnsServices_Username="" | |
76 | DnsServices_Password="" | |
77 | _err "You didn't specify dns.services api username and password yet." | |
78 | _err "Set environment variables DnsServices_Username and DnsServices_Password" | |
79 | return 1 | |
80 | fi | |
81 | ||
82 | # Setup GET/POST/DELETE headers | |
83 | _setup_headers | |
84 | ||
85 | if ! _get_root "${fulldomain}"; then | |
86 | _err "Invalid domain ${fulldomain}" | |
87 | return 1 | |
88 | fi | |
89 | ||
90 | _debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain" | |
91 | ||
92 | if ! deleteRecord "${fulldomain}" "${txtvalue}"; then | |
93 | _err "Error removing record: $fulldomain TXT ${txtvalue}" | |
94 | return 1 | |
95 | fi | |
96 | ||
97 | return 0 | |
98 | } | |
99 | ||
100 | #################### Private functions below ################################## | |
101 | ||
102 | _setup_headers() { | |
103 | # Set up API Headers for _get() and _post() | |
104 | # The <function>_add or <function>_rm must have been called before to work | |
105 | ||
106 | if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then | |
107 | _err "Could not setup BASIC authentication headers, they are missing" | |
108 | return 1 | |
109 | fi | |
110 | ||
111 | DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)" | |
112 | export _H1="Authorization: Basic $DnsServiceCredentials" | |
113 | export _H2="Content-Type: application/json" | |
114 | ||
115 | # Just return if headers are set | |
116 | return 0 | |
117 | } | |
118 | ||
119 | _get_root() { | |
120 | domain=$1 | |
121 | _debug2 _get_root "Get the root domain of ${domain} for DNS API" | |
122 | ||
123 | # Setup _get() and _post() headers | |
124 | #_setup_headers | |
125 | ||
126 | result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns") | |
127 | _debug2 _get_root "Got the following root domain(s) $result" | |
128 | _debug2 _get_root "- JSON: $result" | |
129 | ||
130 | if [ "$(echo "$result" | grep -c '"name"')" -gt "1" ]; then | |
131 | checkMultiZones="true" | |
132 | _debug2 _get_root "- multiple zones found" | |
133 | else | |
134 | checkMultiZones="false" | |
135 | ||
136 | fi | |
137 | ||
138 | # Find/isolate the root zone to work with in createRecord() and deleteRecord() | |
139 | rootZone="" | |
140 | if [ "$checkMultiZones" = "true" ]; then | |
141 | rootZone=$(for zone in $(echo "$result" | tr -d '\n' ' '); do | |
142 | if [ "$(echo "$domain" | grep "$zone")" != "" ]; then | |
143 | _debug2 _get_root "- trying to figure out if $zone is in $domain" | |
144 | echo "$zone" | |
145 | break | |
146 | fi | |
147 | done) | |
148 | else | |
149 | rootZone=$(echo "$result" | grep -o '"name":"[^"]*' | cut -d'"' -f4) | |
150 | _debug2 _get_root "- only found 1 domain in API: $rootZone" | |
151 | fi | |
152 | ||
153 | if [ -z "$rootZone" ]; then | |
154 | _err "Could not find root domain for $domain - is it correctly typed?" | |
155 | return 1 | |
156 | fi | |
157 | ||
158 | # Setup variables used by other functions to communicate with DNS Services API | |
159 | zoneInfo=$(echo "$result" | sed "s,\"zones,\n&,g" | grep zones | cut -d'[' -f2 | cut -d']' -f1 | tr '}' '\n' | grep "\"$rootZone\"") | |
160 | rootZoneName="$rootZone" | |
161 | subDomainName="$(echo "$domain" | sed "s,\.$rootZone,,g")" | |
162 | subDomainNameClean="$(echo "$domain" | sed "s,_acme-challenge.,,g")" | |
163 | rootZoneDomainID=$(echo "$zoneInfo" | tr ',' '\n' | grep domain_id | cut -d'"' -f4) | |
164 | rootZoneServiceID=$(echo "$zoneInfo" | tr ',' '\n' | grep service_id | cut -d'"' -f4) | |
165 | ||
166 | _debug2 _get_root "Root zone name : $rootZoneName" | |
167 | _debug2 _get_root "Root zone domain ID : $rootZoneDomainID" | |
168 | _debug2 _get_root "Root zone service ID: $rootZoneServiceID" | |
169 | _debug2 _get_root "Sub domain : $subDomainName" | |
170 | ||
171 | _debug _get_root "Found valid root domain $rootZone for $subDomainNameClean" | |
172 | return 0 | |
173 | } | |
174 | ||
175 | createRecord() { | |
176 | fulldomain=$1 | |
177 | txtvalue="$2" | |
178 | ||
179 | # Get root domain information - needed for DNS Services API communication | |
180 | if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then | |
181 | _get_root "$fulldomain" | |
182 | fi | |
183 | ||
184 | _debug2 createRecord "CNAME TXT value is: $txtvalue" | |
185 | ||
186 | # Prepare data to send to API | |
187 | data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}" | |
188 | ||
189 | _debug2 createRecord "data to API: $data" | |
190 | result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST") | |
191 | _debug2 createRecord "result from API: $result" | |
192 | ||
193 | if [ "$(echo "$result" | grep '"success":true')" = "" ]; then | |
194 | _err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName" | |
195 | _err "$result" | |
196 | return 1 | |
197 | fi | |
198 | ||
199 | _info "Record \"$fulldomain TXT $txtvalue\" has been created" | |
200 | return 0 | |
201 | } | |
202 | ||
203 | deleteRecord() { | |
204 | fulldomain=$1 | |
205 | txtvalue=$2 | |
206 | ||
207 | if [ "$(echo "$fulldomain" | grep "_acme-challenge")" = "" ]; then | |
208 | _err "The script tried to delete the record $fulldomain which is not the above created ACME challenge" | |
209 | return 1 | |
210 | fi | |
211 | ||
212 | _debug2 deleteRecord "Deleting $fulldomain TXT $txtvalue record" | |
213 | ||
214 | if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then | |
215 | _get_root "$fulldomain" | |
216 | fi | |
217 | ||
218 | result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")" | |
219 | recordInfo="$(echo "$result" | tr '}' '\n' | grep "\"name\":\"${fulldomain}" | grep "\"content\":\"" | grep "${txtvalue}")" | |
220 | _debug2 deleteRecord "recordInfo=$recordInfo" | |
221 | recordID="$(echo "$recordInfo" | tr ',' '\n' | grep -E "\"id\":\"[0-9]+\"" | cut -d'"' -f4)" | |
222 | ||
223 | if [ -z "$recordID" ]; then | |
224 | _info "Record $fulldomain TXT $txtvalue not found or already deleted" | |
225 | return 0 | |
226 | else | |
227 | _debug2 deleteRecord "Found recordID=$recordID" | |
228 | fi | |
229 | ||
230 | _debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" | |
231 | result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")" | |
232 | _debug2 deleteRecord "API Delete result \"$result\"" | |
233 | ||
234 | # Return OK regardless | |
235 | return 0 | |
236 | } |