]> git.proxmox.com Git - proxmox-acme.git/blob - src/proxmox-acme
dns plugin: improve 'data' string encoding/passing
[proxmox-acme.git] / src / proxmox-acme
1 #!/usr/bin/env sh
2
3 VER=0.9
4
5 PROJECT_NAME="ProxmoxACME"
6
7 USER_AGENT="$PROJECT_NAME/$VER"
8
9 DNS_PLUGIN_PATH="/usr/share/proxmox-acme/dnsapi"
10 HTTP_HEADER="$(mktemp)"
11
12 DEBUG="0"
13
14 _base64() {
15 openssl base64 -e | tr -d '\r\n'
16 }
17
18 _dbase64() {
19 openssl base64 -d
20 }
21
22 # Usage: hashalg [outputhex]
23 # Output Base64-encoded digest
24 _digest() {
25 alg="$1"
26
27 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
28 if [ "$2" ]; then
29 openssl dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
30 else
31 openssl dgst -"$alg" -binary | _base64
32 fi
33 fi
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
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"
90 if eval type type >/dev/null 2>&1; then
91 type "$cmd" >/dev/null 2>&1
92 else command
93 command -v "$cmd" >/dev/null 2>&1
94 fi
95 ret="$?"
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
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
122
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)"
134 else
135 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
136 fi
137 else
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)"
140 else
141 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
142 fi
143 fi
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")"
148 else
149 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
150 fi
151 else
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")"
154 else
155 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
156 fi
157 fi
158 fi
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
163 printf "%s" "$response"
164 return $_ret
165 }
166
167 # url getheader timeout
168 _get() {
169 url="$1"
170 onlyheader="$2"
171 t="$3"
172
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"
182 else
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"
188 fi
189 return $ret
190 }
191
192 _head_n() {
193 head -n "$1"
194 }
195
196 _tail_n() {
197 tail -n "$1"
198 }
199
200 # stdin output hexstr splited by one space
201 # input:"abc"
202 # output: " 61 62 63"
203 _hex_dump() {
204 od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n"
205 }
206
207 # stdin stdout
208 _url_encode() {
209 _hex_str=$(_hex_dump)
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 ;;
371
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 ;;
415
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
431 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
432 if [ "$outputhex" ]; then
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 ' '
434 else
435 openssl dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || openssl dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
436 fi
437 fi
438 }
439
440 # domain
441 _is_idn() {
442 _is_idn_d="$1"
443 _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
444 [ "$_idn_temp" ]
445 }
446
447 # aa.com
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
456 idn "$__idn_d" | tr -d "\r\n"
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() {
468 sed -i "$1" "$2"
469 }
470
471 # sleep sec
472 _sleep() {
473 sleep "$1"
474 }
475
476 _stat() {
477 stat -c '%U:%G' "$1" 2>/dev/null
478 }
479
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() {
490 printf -- "%b" "$1"
491 }
492
493 __red() {
494 printf -- "%b" "$1"
495 }
496
497 _log() {
498 return
499 }
500
501 _info() {
502 printf -- "%s" "[$(date)] " >&1
503 echo "$1"
504 }
505
506 _err() {
507 printf -- "%s" "[$(date)] " >&2
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() {
519 echo "$1"
520 }
521
522 # key
523 _readaccountconf_mutable() {
524 _readaccountconf "$1"
525 }
526
527 # no-ops:
528 _clearaccountconf() {
529 return
530 }
531
532 _cleardomainconf() {
533 return
534 }
535
536 _debug() {
537 if [[ $DEBUG -eq 0 ]]; then
538 return
539 fi
540 printf -- "%s" "[$(date)] " >&1
541 echo "$1 $2"
542 }
543
544 _debug2() {
545 _debug $1 $2
546 }
547
548 _debug3() {
549 _debug $1 $2
550 }
551
552 _secure_debug() {
553 _debug $1 $2
554 }
555
556 _secure_debug2() {
557 _debug $1 $2
558 }
559
560 _secure_debug3() {
561 _debug $1 $2
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
584 # Proxmox implementation to inject the DNSAPI variables
585 _load_plugin_config() {
586 while IFS= read -r line; do
587 ADDR=(${line/=/ })
588 key="${ADDR[0]}"
589 value="${ADDR[1]}"
590
591 # acme.sh uses eval insted of export
592 if [ -n "$key" ]; then
593 export "$key"="$value"
594 fi
595 done
596 }
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 # $5 <Integer> 0 is off, and the default all others are on.
605
606 setup() {
607 dns_plugin="dns_$1"
608 dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
609 fqdn="_acme-challenge.$2"
610 DEBUG=$3
611 IFS= read -r txtvalue
612 plugin_conf_string=$4
613
614 _load_plugin_config
615
616 if ! . "$dns_plugin_path"; then
617 _err "Load file $dns_plugin error."
618 return 1
619 fi
620
621 addcommand="${dns_plugin}_add"
622 if ! _exists "$addcommand"; then
623 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
624 return 1
625 fi
626
627 if ! $addcommand "$fqdn" "$txtvalue"; then
628 _err "Error add txt for domain:$fulldomain"
629 return 1
630 fi
631 }
632
633 teardown() {
634 dns_plugin="dns_$1"
635 dns_plugin_path="${DNS_PLUGIN_PATH}/${dns_plugin}.sh"
636 fqdn="_acme-challenge.$2"
637 DEBUG=$3
638 IFS= read -r txtvalue
639
640 _load_plugin_config
641
642 if ! . "$dns_plugin_path"; then
643 _err "Load file $dns_plugin error."
644 return 1
645 fi
646
647 rmcommand="${dns_plugin}_rm"
648 if ! _exists "$rmcommand"; then
649 _err "It seems that your api file is not correct, it must have a function named: $rmcommand"
650 return 1
651 fi
652
653 if ! $rmcommand "$fqdn" "$txtvalue"; then
654 _err "Error add txt for domain:$fulldomain"
655 return 1
656 fi
657 }
658
659 "$@"