]> git.proxmox.com Git - proxmox-acme.git/blame - src/proxmox-acme
dns plugin: make data optional
[proxmox-acme.git] / src / proxmox-acme
CommitLineData
92b88a9e
WL
1#!/usr/bin/env sh
2
ece42f2f
WL
3VER=0.9
4
5PROJECT_NAME="ProxmoxACME"
6
7USER_AGENT="$PROJECT_NAME/$VER"
8
9DNS_PLUGIN_PATH="/usr/share/proxmox-acme/dnsapi"
bb2e0864
WL
10HTTP_HEADER="$(mktemp)"
11
54fd0088
WL
12DEBUG="0"
13
92b88a9e 14_base64() {
bb2e0864 15 openssl base64 -e | tr -d '\r\n'
92b88a9e
WL
16}
17
92b88a9e 18_dbase64() {
bb2e0864 19 openssl base64 -d
92b88a9e
WL
20}
21
22# Usage: hashalg [outputhex]
23# Output Base64-encoded digest
24_digest() {
25 alg="$1"
92b88a9e
WL
26
27 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
bb2e0864
WL
28 if [ "$2" ]; then
29 openssl dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
92b88a9e 30 else
bb2e0864 31 openssl dgst -"$alg" -binary | _base64
92b88a9e 32 fi
92b88a9e 33 fi
92b88a9e
WL
34}
35
36_upper_case() {
37 # shellcheck disable=SC2018,SC2019
38 tr 'a-z' 'A-Z'
39}
40
41_lower_case() {
42 # shellcheck disable=SC2018,SC2019
43 tr 'A-Z' 'a-z'
44}
45
46_startswith() {
47 _str="$1"
48 _sub="$2"
49 echo "$_str" | grep "^$_sub" >/dev/null 2>&1
50}
51
52_endswith() {
53 _str="$1"
54 _sub="$2"
55 echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
56}
57
58_contains() {
59 _str="$1"
60 _sub="$2"
61 echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
62}
63
64# str index [sep]
65_getfield() {
66 _str="$1"
67 _findex="$2"
68 _sep="$3"
69
92b88a9e
WL
70 if [ -z "$_sep" ]; then
71 _sep=","
72 fi
73
74 _ffi="$_findex"
75 while [ "$_ffi" -gt "0" ]; do
76 _fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
77 if [ "$_fv" ]; then
78 printf -- "%s" "$_fv"
79 return 0
80 fi
81 _ffi="$(_math "$_ffi" - 1)"
82 done
83
84 printf -- "%s" "$_str"
85
86}
87
88_exists() {
89 cmd="$1"
92b88a9e 90 if eval type type >/dev/null 2>&1; then
bb2e0864
WL
91 type "$cmd" >/dev/null 2>&1
92 else command
92b88a9e 93 command -v "$cmd" >/dev/null 2>&1
92b88a9e
WL
94 fi
95 ret="$?"
92b88a9e
WL
96 return $ret
97}
98
99# a + b
100_math() {
101 _m_opts="$@"
102 printf "%s" "$(($_m_opts))"
103}
104
105_egrep_o() {
106 if ! egrep -o "$1" 2>/dev/null; then
107 sed -n 's/.*\('"$1"'\).*/\1/p'
108 fi
109}
110
92b88a9e
WL
111# body url [needbase64] [POST|PUT|DELETE] [ContentType]
112_post() {
113 body="$1"
114 _post_url="$2"
115 needbase64="$3"
116 httpmethod="$4"
117 _postContentType="$5"
118
119 if [ -z "$httpmethod" ]; then
120 httpmethod="POST"
121 fi
92b88a9e 122
bb2e0864
WL
123 _CURL="curl -L --silent --dump-header $HTTP_HEADER -g "
124 if [ "$HTTPS_INSECURE" ]; then
125 _CURL="$_CURL --insecure "
126 fi
127 if [ "$httpmethod" = "HEAD" ]; then
128 _CURL="$_CURL -I "
129 fi
130 if [ "$needbase64" ]; then
131 if [ "$body" ]; then
132 if [ "$_postContentType" ]; then
133 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
92b88a9e 134 else
bb2e0864 135 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
92b88a9e
WL
136 fi
137 else
bb2e0864
WL
138 if [ "$_postContentType" ]; then
139 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
92b88a9e 140 else
bb2e0864 141 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
92b88a9e
WL
142 fi
143 fi
bb2e0864
WL
144 else
145 if [ "$body" ]; then
146 if [ "$_postContentType" ]; then
147 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
92b88a9e 148 else
bb2e0864 149 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
92b88a9e
WL
150 fi
151 else
bb2e0864
WL
152 if [ "$_postContentType" ]; then
153 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
92b88a9e 154 else
bb2e0864 155 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
92b88a9e
WL
156 fi
157 fi
92b88a9e 158 fi
bb2e0864
WL
159 _ret="$?"
160 if [ "$_ret" != "0" ]; then
161 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
162 fi
92b88a9e
WL
163 printf "%s" "$response"
164 return $_ret
165}
166
167# url getheader timeout
168_get() {
92b88a9e
WL
169 url="$1"
170 onlyheader="$2"
171 t="$3"
92b88a9e 172
bb2e0864
WL
173 _CURL="curl -L --silent --dump-header $HTTP_HEADER -g "
174 if [ "$HTTPS_INSECURE" ]; then
175 _CURL="$_CURL --insecure "
176 fi
177 if [ "$t" ]; then
178 _CURL="$_CURL --connect-timeout $t"
179 fi
180 if [ "$onlyheader" ]; then
181 $_CURL -I --user-agent "USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
92b88a9e 182 else
bb2e0864
WL
183 $_CURL --user-agent "USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
184 fi
185 ret=$?
186 if [ "$ret" != "0" ]; then
187 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
92b88a9e 188 fi
92b88a9e
WL
189 return $ret
190}
191
192_head_n() {
193 head -n "$1"
194}
195
196_tail_n() {
bb2e0864 197 tail -n "$1"
92b88a9e
WL
198}
199
200# stdin output hexstr splited by one space
201# input:"abc"
202# output: " 61 62 63"
203_hex_dump() {
bb2e0864 204 od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n"
92b88a9e
WL
205}
206
207# stdin stdout
208_url_encode() {
209 _hex_str=$(_hex_dump)
92b88a9e
WL
210 for _hex_code in $_hex_str; do
211 #upper case
212 case "${_hex_code}" in
213 "41")
214 printf "%s" "A"
215 ;;
216 "42")
217 printf "%s" "B"
218 ;;
219 "43")
220 printf "%s" "C"
221 ;;
222 "44")
223 printf "%s" "D"
224 ;;
225 "45")
226 printf "%s" "E"
227 ;;
228 "46")
229 printf "%s" "F"
230 ;;
231 "47")
232 printf "%s" "G"
233 ;;
234 "48")
235 printf "%s" "H"
236 ;;
237 "49")
238 printf "%s" "I"
239 ;;
240 "4a")
241 printf "%s" "J"
242 ;;
243 "4b")
244 printf "%s" "K"
245 ;;
246 "4c")
247 printf "%s" "L"
248 ;;
249 "4d")
250 printf "%s" "M"
251 ;;
252 "4e")
253 printf "%s" "N"
254 ;;
255 "4f")
256 printf "%s" "O"
257 ;;
258 "50")
259 printf "%s" "P"
260 ;;
261 "51")
262 printf "%s" "Q"
263 ;;
264 "52")
265 printf "%s" "R"
266 ;;
267 "53")
268 printf "%s" "S"
269 ;;
270 "54")
271 printf "%s" "T"
272 ;;
273 "55")
274 printf "%s" "U"
275 ;;
276 "56")
277 printf "%s" "V"
278 ;;
279 "57")
280 printf "%s" "W"
281 ;;
282 "58")
283 printf "%s" "X"
284 ;;
285 "59")
286 printf "%s" "Y"
287 ;;
288 "5a")
289 printf "%s" "Z"
290 ;;
291
292 #lower case
293 "61")
294 printf "%s" "a"
295 ;;
296 "62")
297 printf "%s" "b"
298 ;;
299 "63")
300 printf "%s" "c"
301 ;;
302 "64")
303 printf "%s" "d"
304 ;;
305 "65")
306 printf "%s" "e"
307 ;;
308 "66")
309 printf "%s" "f"
310 ;;
311 "67")
312 printf "%s" "g"
313 ;;
314 "68")
315 printf "%s" "h"
316 ;;
317 "69")
318 printf "%s" "i"
319 ;;
320 "6a")
321 printf "%s" "j"
322 ;;
323 "6b")
324 printf "%s" "k"
325 ;;
326 "6c")
327 printf "%s" "l"
328 ;;
329 "6d")
330 printf "%s" "m"
331 ;;
332 "6e")
333 printf "%s" "n"
334 ;;
335 "6f")
336 printf "%s" "o"
337 ;;
338 "70")
339 printf "%s" "p"
340 ;;
341 "71")
342 printf "%s" "q"
343 ;;
344 "72")
345 printf "%s" "r"
346 ;;
347 "73")
348 printf "%s" "s"
349 ;;
350 "74")
351 printf "%s" "t"
352 ;;
353 "75")
354 printf "%s" "u"
355 ;;
356 "76")
357 printf "%s" "v"
358 ;;
359 "77")
360 printf "%s" "w"
361 ;;
362 "78")
363 printf "%s" "x"
364 ;;
365 "79")
366 printf "%s" "y"
367 ;;
368 "7a")
369 printf "%s" "z"
370 ;;
bb2e0864 371
92b88a9e
WL
372 #numbers
373 "30")
374 printf "%s" "0"
375 ;;
376 "31")
377 printf "%s" "1"
378 ;;
379 "32")
380 printf "%s" "2"
381 ;;
382 "33")
383 printf "%s" "3"
384 ;;
385 "34")
386 printf "%s" "4"
387 ;;
388 "35")
389 printf "%s" "5"
390 ;;
391 "36")
392 printf "%s" "6"
393 ;;
394 "37")
395 printf "%s" "7"
396 ;;
397 "38")
398 printf "%s" "8"
399 ;;
400 "39")
401 printf "%s" "9"
402 ;;
403 "2d")
404 printf "%s" "-"
405 ;;
406 "5f")
407 printf "%s" "_"
408 ;;
409 "2e")
410 printf "%s" "."
411 ;;
412 "7e")
413 printf "%s" "~"
414 ;;
bb2e0864 415
92b88a9e
WL
416 #other hex
417 *)
418 printf '%%%s' "$_hex_code"
419 ;;
420 esac
421 done
422}
423
424# Usage: hashalg secret_hex [outputhex]
425# Output binary hmac
426_hmac() {
427 alg="$1"
428 secret_hex="$2"
429 outputhex="$3"
430
92b88a9e
WL
431 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
432 if [ "$outputhex" ]; then
bb2e0864 433 (openssl dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || openssl dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
92b88a9e 434 else
bb2e0864 435 openssl dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || openssl dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
92b88a9e 436 fi
92b88a9e 437 fi
92b88a9e
WL
438}
439
440# domain
441_is_idn() {
442 _is_idn_d="$1"
92b88a9e 443 _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
92b88a9e
WL
444 [ "$_idn_temp" ]
445}
446
447# aa.com
92b88a9e
WL
448_idn() {
449 __idn_d="$1"
450 if ! _is_idn "$__idn_d"; then
451 printf "%s" "$__idn_d"
452 return 0
453 fi
454
455 if _exists idn; then
bb2e0864 456 idn "$__idn_d" | tr -d "\r\n"
92b88a9e
WL
457 else
458 _err "Please install idn to process IDN names."
459 fi
460}
461
462_normalizeJson() {
463 sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
464}
465
466# options file
467_sed_i() {
bb2e0864 468 sed -i "$1" "$2"
92b88a9e
WL
469}
470
471# sleep sec
472_sleep() {
bb2e0864 473 sleep "$1"
92b88a9e
WL
474}
475
476_stat() {
bb2e0864 477 stat -c '%U:%G' "$1" 2>/dev/null
92b88a9e
WL
478}
479
92b88a9e
WL
480_time() {
481 date -u "+%s"
482}
483
484_utc_date() {
485 date -u "+%Y-%m-%d %H:%M:%S"
486}
487
488# stubbed/aliased:
489__green() {
92b88a9e
WL
490 printf -- "%b" "$1"
491}
492
493__red() {
92b88a9e
WL
494 printf -- "%b" "$1"
495}
496
497_log() {
bb2e0864 498 return
92b88a9e
WL
499}
500
501_info() {
bb2e0864
WL
502 printf -- "%s" "[$(date)] " >&1
503 echo "$1"
92b88a9e
WL
504}
505
506_err() {
bb2e0864 507 printf -- "%s" "[$(date)] " >&2
92b88a9e
WL
508 if [ -z "$2" ]; then
509 __red "$1" >&2
510 else
511 __red "$1='$2'" >&2
512 fi
513 printf "\n" >&2
514 return 1
515}
516
517# key
518_readaccountconf() {
bb2e0864 519 echo "$1"
92b88a9e
WL
520}
521
522# key
523_readaccountconf_mutable() {
bb2e0864
WL
524 _readaccountconf "$1"
525}
526
527# no-ops:
528_clearaccountconf() {
529 return
530}
531
532_cleardomainconf() {
533 return
534}
535
536_debug() {
54fd0088
WL
537 if [[ $DEBUG -eq 0 ]]; then
538 return
539 fi
540 printf -- "%s" "[$(date)] " >&1
541 echo "$1 $2"
bb2e0864
WL
542}
543
544_debug2() {
54fd0088 545 _debug $1 $2
bb2e0864
WL
546}
547
548_debug3() {
54fd0088 549 _debug $1 $2
bb2e0864
WL
550}
551
552_secure_debug() {
54fd0088 553 _debug $1 $2
92b88a9e 554}
bb2e0864
WL
555
556_secure_debug2() {
54fd0088 557 _debug $1 $2
bb2e0864
WL
558}
559
560_secure_debug3() {
54fd0088 561 _debug $1 $2
bb2e0864
WL
562}
563
564_saveaccountconf() {
565 return
566}
567
568_saveaccountconf_mutable() {
569 return
570}
571
572_save_conf() {
573 return
574}
575
576_savedomainconf() {
577 return
578}
579
580_source_plugin_config() {
581 return
582}
583
216d4f1d
WL
584# Proxmox implementation to inject the DNSAPI variables
585_load_plugin_config() {
586 tmp_str="${plugin_conf_string//[^,]}"
587 index="$(_math ${#tmp_str} + 1)"
588 while [ "$index" -gt "0" ]
589 do
590 field=$(_getfield $plugin_conf_string "$index" ",")
591 ADDR=(${field/=/ })
592 key="${ADDR[0]}"
593 value="${ADDR[1]}"
594
595 # decode base64 encoded values
596 value=$(echo $value | /usr/bin/openssl base64 -d -A)
597
598 # acme.sh uses eval insted of export
599 export "$key"="$value"
600 index="$(_math "$index" - 1)"
601 done
602}
ece42f2f
WL
603
604# call setup and teardown direct
605# the parameter must be set in the correct order
606# $1 <String> DNS Plugin name
607# $2 <String> Fully Qualified Domain Name
608# $3 <String> value for TXT record
609# $4 <String> DNS plugin auth and config parameter separated by ","
54fd0088 610# $5 <Integer> 0 is off, and the default all others are on.
ece42f2f
WL
611
612setup() {
613 dns_plugin="dns_$1"
614 dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
615 fqdn="_acme-challenge.$2"
616 txtvalue=$3
617 plugin_conf_string=$4
54fd0088 618 DEBUG=$5
ece42f2f 619
13b63882
FG
620 if [ -n "$plugin_conf_string" ]; then
621 _load_plugin_config
622 fi
ece42f2f
WL
623
624 if ! . "$dns_plugin_path"; then
625 _err "Load file $dns_plugin error."
626 return 1
627 fi
628
629 addcommand="${dns_plugin}_add"
630 if ! _exists "$addcommand"; then
631 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
632 return 1
633 fi
634
635 if ! $addcommand "$fqdn" "$txtvalue"; then
636 _err "Error add txt for domain:$fulldomain"
637 return 1
638 fi
639}
640
641teardown() {
642 dns_plugin="dns_$1"
643 dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
644 fqdn="_acme-challenge.$2"
645 txtvalue=$3
646 plugin_conf_string=$4
54fd0088 647 DEBUG=$5
ece42f2f 648
13b63882
FG
649 if [ -n "$plugin_conf_string" ]; then
650 _load_plugin_config
651 fi
ece42f2f
WL
652
653 if ! . "$dns_plugin_path"; then
654 _err "Load file $dns_plugin error."
655 return 1
656 fi
657
658 rmcommand="${dns_plugin}_rm"
659 if ! _exists "$rmcommand"; then
660 _err "It seems that your api file is not correct, it must have a function named: $rmcommand"
661 return 1
662 fi
663
664 if ! $rmcommand "$fqdn" "$txtvalue"; then
665 _err "Error add txt for domain:$fulldomain"
666 return 1
667 fi
668}
669
670"$@"