]>
Commit | Line | Data |
---|---|---|
92b88a9e WL |
1 | #!/usr/bin/env sh |
2 | ||
ece42f2f WL |
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" | |
bb2e0864 WL |
10 | HTTP_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 | ||
605 | setup() { | |
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 | ||
631 | teardown() { | |
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 | "$@" |