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