]> git.proxmox.com Git - mirror_acme.sh.git/blob - acme.sh
fix preferred chain for renewal
[mirror_acme.sh.git] / acme.sh
1 #!/usr/bin/env sh
2
3 VER=2.8.7
4
5 PROJECT_NAME="acme.sh"
6
7 PROJECT_ENTRY="acme.sh"
8
9 PROJECT="https://github.com/acmesh-official/$PROJECT_NAME"
10
11 DEFAULT_INSTALL_HOME="$HOME/.$PROJECT_NAME"
12
13 _WINDOWS_SCHEDULER_NAME="$PROJECT_NAME.cron"
14
15 _SCRIPT_="$0"
16
17 _SUB_FOLDER_NOTIFY="notify"
18 _SUB_FOLDER_DNSAPI="dnsapi"
19 _SUB_FOLDER_DEPLOY="deploy"
20
21 _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY"
22
23 LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory"
24 LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory"
25
26 CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory"
27 CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory"
28
29 CA_BUYPASS="https://api.buypass.com/acme/directory"
30 CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory"
31
32 CA_ZEROSSL="https://acme.zerossl.com/v2/DV90"
33 _ZERO_EAB_ENDPOINT="http://api.zerossl.com/acme/eab-credentials-email"
34
35 DEFAULT_CA=$CA_LETSENCRYPT_V2
36 DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST
37
38 CA_NAMES="
39 LetsEncrypt.org,letsencrypt
40 LetsEncrypt.org_test,letsencrypt_test,letsencrypttest
41 BuyPass.com,buypass
42 BuyPass.com_test,buypass_test,buypasstest
43 ZeroSSL.com,zerossl
44 "
45
46 CA_SERVERS="$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_ZEROSSL"
47
48 DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
49
50 DEFAULT_ACCOUNT_KEY_LENGTH=2048
51 DEFAULT_DOMAIN_KEY_LENGTH=2048
52
53 DEFAULT_OPENSSL_BIN="openssl"
54
55 VTYPE_HTTP="http-01"
56 VTYPE_DNS="dns-01"
57 VTYPE_ALPN="tls-alpn-01"
58
59 LOCAL_ANY_ADDRESS="0.0.0.0"
60
61 DEFAULT_RENEW=60
62
63 NO_VALUE="no"
64
65 W_DNS="dns"
66 W_ALPN="alpn"
67 DNS_ALIAS_PREFIX="="
68
69 MODE_STATELESS="stateless"
70
71 STATE_VERIFIED="verified_ok"
72
73 NGINX="nginx:"
74 NGINX_START="#ACME_NGINX_START"
75 NGINX_END="#ACME_NGINX_END"
76
77 BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----"
78 END_CSR="-----END CERTIFICATE REQUEST-----"
79
80 BEGIN_CERT="-----BEGIN CERTIFICATE-----"
81 END_CERT="-----END CERTIFICATE-----"
82
83 CONTENT_TYPE_JSON="application/jose+json"
84 RENEW_SKIP=2
85
86 B64CONF_START="__ACME_BASE64__START_"
87 B64CONF_END="__ACME_BASE64__END_"
88
89 ECC_SEP="_"
90 ECC_SUFFIX="${ECC_SEP}ecc"
91
92 LOG_LEVEL_1=1
93 LOG_LEVEL_2=2
94 LOG_LEVEL_3=3
95 DEFAULT_LOG_LEVEL="$LOG_LEVEL_1"
96
97 DEBUG_LEVEL_1=1
98 DEBUG_LEVEL_2=2
99 DEBUG_LEVEL_3=3
100 DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_1
101 DEBUG_LEVEL_NONE=0
102
103 DOH_CLOUDFLARE=1
104 DOH_GOOGLE=2
105
106 HIDDEN_VALUE="[hidden](please add '--output-insecure' to see this value)"
107
108 SYSLOG_ERROR="user.error"
109 SYSLOG_INFO="user.info"
110 SYSLOG_DEBUG="user.debug"
111
112 #error
113 SYSLOG_LEVEL_ERROR=3
114 #info
115 SYSLOG_LEVEL_INFO=6
116 #debug
117 SYSLOG_LEVEL_DEBUG=7
118 #debug2
119 SYSLOG_LEVEL_DEBUG_2=8
120 #debug3
121 SYSLOG_LEVEL_DEBUG_3=9
122
123 SYSLOG_LEVEL_DEFAULT=$SYSLOG_LEVEL_ERROR
124 #none
125 SYSLOG_LEVEL_NONE=0
126
127 NOTIFY_LEVEL_DISABLE=0
128 NOTIFY_LEVEL_ERROR=1
129 NOTIFY_LEVEL_RENEW=2
130 NOTIFY_LEVEL_SKIP=3
131
132 NOTIFY_LEVEL_DEFAULT=$NOTIFY_LEVEL_RENEW
133
134 NOTIFY_MODE_BULK=0
135 NOTIFY_MODE_CERT=1
136
137 NOTIFY_MODE_DEFAULT=$NOTIFY_MODE_BULK
138
139 _DEBUG_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh"
140
141 _PREPARE_LINK="https://github.com/acmesh-official/acme.sh/wiki/Install-preparations"
142
143 _STATELESS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode"
144
145 _DNS_ALIAS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode"
146
147 _DNS_MANUAL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode"
148
149 _DNS_API_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnsapi"
150
151 _NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify"
152
153 _SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo"
154
155 _REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert"
156
157 _ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA"
158
159 _SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server"
160
161 _PREFERRED_CHAIN_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain"
162
163 _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
164
165 _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
166
167 _DNS_MANUAL_ERROR="It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI"
168
169 __INTERACTIVE=""
170 if [ -t 1 ]; then
171 __INTERACTIVE="1"
172 fi
173
174 __green() {
175 if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
176 printf '\33[1;32m%b\33[0m' "$1"
177 return
178 fi
179 printf -- "%b" "$1"
180 }
181
182 __red() {
183 if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
184 printf '\33[1;31m%b\33[0m' "$1"
185 return
186 fi
187 printf -- "%b" "$1"
188 }
189
190 _printargs() {
191 _exitstatus="$?"
192 if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then
193 printf -- "%s" "[$(date)] "
194 fi
195 if [ -z "$2" ]; then
196 printf -- "%s" "$1"
197 else
198 printf -- "%s" "$1='$2'"
199 fi
200 printf "\n"
201 # return the saved exit status
202 return "$_exitstatus"
203 }
204
205 _dlg_versions() {
206 echo "Diagnosis versions: "
207 echo "openssl:$ACME_OPENSSL_BIN"
208 if _exists "${ACME_OPENSSL_BIN:-openssl}"; then
209 ${ACME_OPENSSL_BIN:-openssl} version 2>&1
210 else
211 echo "$ACME_OPENSSL_BIN doesn't exist."
212 fi
213
214 echo "apache:"
215 if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then
216 $_APACHECTL -V 2>&1
217 else
218 echo "apache doesn't exist."
219 fi
220
221 echo "nginx:"
222 if _exists "nginx"; then
223 nginx -V 2>&1
224 else
225 echo "nginx doesn't exist."
226 fi
227
228 echo "socat:"
229 if _exists "socat"; then
230 socat -V 2>&1
231 else
232 _debug "socat doesn't exist."
233 fi
234 }
235
236 #class
237 _syslog() {
238 _exitstatus="$?"
239 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" = "$SYSLOG_LEVEL_NONE" ]; then
240 return
241 fi
242 _logclass="$1"
243 shift
244 if [ -z "$__logger_i" ]; then
245 if _contains "$(logger --help 2>&1)" "-i"; then
246 __logger_i="logger -i"
247 else
248 __logger_i="logger"
249 fi
250 fi
251 $__logger_i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1
252 return "$_exitstatus"
253 }
254
255 _log() {
256 [ -z "$LOG_FILE" ] && return
257 _printargs "$@" >>"$LOG_FILE"
258 }
259
260 _info() {
261 _log "$@"
262 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_INFO" ]; then
263 _syslog "$SYSLOG_INFO" "$@"
264 fi
265 _printargs "$@"
266 }
267
268 _err() {
269 _syslog "$SYSLOG_ERROR" "$@"
270 _log "$@"
271 if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then
272 printf -- "%s" "[$(date)] " >&2
273 fi
274 if [ -z "$2" ]; then
275 __red "$1" >&2
276 else
277 __red "$1='$2'" >&2
278 fi
279 printf "\n" >&2
280 return 1
281 }
282
283 _usage() {
284 __red "$@" >&2
285 printf "\n" >&2
286 }
287
288 __debug_bash_helper() {
289 # At this point only do for --debug 3
290 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -lt "$DEBUG_LEVEL_3" ]; then
291 return
292 fi
293 # Return extra debug info when running with bash, otherwise return empty
294 # string.
295 if [ -z "${BASH_VERSION}" ]; then
296 return
297 fi
298 # We are a bash shell at this point, return the filename, function name, and
299 # line number as a string
300 _dbh_saveIFS=$IFS
301 IFS=" "
302 # Must use eval or syntax error happens under dash. The eval should use
303 # single quotes as older versions of busybox had a bug with double quotes and
304 # eval.
305 # Use 'caller 1' as we want one level up the stack as we should be called
306 # by one of the _debug* functions
307 eval '_dbh_called=($(caller 1))'
308 IFS=$_dbh_saveIFS
309 eval '_dbh_file=${_dbh_called[2]}'
310 if [ -n "${_script_home}" ]; then
311 # Trim off the _script_home directory name
312 eval '_dbh_file=${_dbh_file#$_script_home/}'
313 fi
314 eval '_dbh_function=${_dbh_called[1]}'
315 eval '_dbh_lineno=${_dbh_called[0]}'
316 printf "%-40s " "$_dbh_file:${_dbh_function}:${_dbh_lineno}"
317 }
318
319 _debug() {
320 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
321 _log "$@"
322 fi
323 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then
324 _syslog "$SYSLOG_DEBUG" "$@"
325 fi
326 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
327 _bash_debug=$(__debug_bash_helper)
328 _printargs "${_bash_debug}$@" >&2
329 fi
330 }
331
332 #output the sensitive messages
333 _secure_debug() {
334 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
335 if [ "$OUTPUT_INSECURE" = "1" ]; then
336 _log "$@"
337 else
338 _log "$1" "$HIDDEN_VALUE"
339 fi
340 fi
341 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then
342 _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
343 fi
344 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
345 if [ "$OUTPUT_INSECURE" = "1" ]; then
346 _printargs "$@" >&2
347 else
348 _printargs "$1" "$HIDDEN_VALUE" >&2
349 fi
350 fi
351 }
352
353 _debug2() {
354 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then
355 _log "$@"
356 fi
357 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then
358 _syslog "$SYSLOG_DEBUG" "$@"
359 fi
360 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
361 _bash_debug=$(__debug_bash_helper)
362 _printargs "${_bash_debug}$@" >&2
363 fi
364 }
365
366 _secure_debug2() {
367 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then
368 if [ "$OUTPUT_INSECURE" = "1" ]; then
369 _log "$@"
370 else
371 _log "$1" "$HIDDEN_VALUE"
372 fi
373 fi
374 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then
375 _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
376 fi
377 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
378 if [ "$OUTPUT_INSECURE" = "1" ]; then
379 _printargs "$@" >&2
380 else
381 _printargs "$1" "$HIDDEN_VALUE" >&2
382 fi
383 fi
384 }
385
386 _debug3() {
387 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then
388 _log "$@"
389 fi
390 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then
391 _syslog "$SYSLOG_DEBUG" "$@"
392 fi
393 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
394 _bash_debug=$(__debug_bash_helper)
395 _printargs "${_bash_debug}$@" >&2
396 fi
397 }
398
399 _secure_debug3() {
400 if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then
401 if [ "$OUTPUT_INSECURE" = "1" ]; then
402 _log "$@"
403 else
404 _log "$1" "$HIDDEN_VALUE"
405 fi
406 fi
407 if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then
408 _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE"
409 fi
410 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
411 if [ "$OUTPUT_INSECURE" = "1" ]; then
412 _printargs "$@" >&2
413 else
414 _printargs "$1" "$HIDDEN_VALUE" >&2
415 fi
416 fi
417 }
418
419 _upper_case() {
420 # shellcheck disable=SC2018,SC2019
421 tr 'a-z' 'A-Z'
422 }
423
424 _lower_case() {
425 # shellcheck disable=SC2018,SC2019
426 tr 'A-Z' 'a-z'
427 }
428
429 _startswith() {
430 _str="$1"
431 _sub="$2"
432 echo "$_str" | grep "^$_sub" >/dev/null 2>&1
433 }
434
435 _endswith() {
436 _str="$1"
437 _sub="$2"
438 echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1
439 }
440
441 _contains() {
442 _str="$1"
443 _sub="$2"
444 echo "$_str" | grep -- "$_sub" >/dev/null 2>&1
445 }
446
447 _hasfield() {
448 _str="$1"
449 _field="$2"
450 _sep="$3"
451 if [ -z "$_field" ]; then
452 _usage "Usage: str field [sep]"
453 return 1
454 fi
455
456 if [ -z "$_sep" ]; then
457 _sep=","
458 fi
459
460 for f in $(echo "$_str" | tr "$_sep" ' '); do
461 if [ "$f" = "$_field" ]; then
462 _debug2 "'$_str' contains '$_field'"
463 return 0 #contains ok
464 fi
465 done
466 _debug2 "'$_str' does not contain '$_field'"
467 return 1 #not contains
468 }
469
470 # str index [sep]
471 _getfield() {
472 _str="$1"
473 _findex="$2"
474 _sep="$3"
475
476 if [ -z "$_findex" ]; then
477 _usage "Usage: str field [sep]"
478 return 1
479 fi
480
481 if [ -z "$_sep" ]; then
482 _sep=","
483 fi
484
485 _ffi="$_findex"
486 while [ "$_ffi" -gt "0" ]; do
487 _fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")"
488 if [ "$_fv" ]; then
489 printf -- "%s" "$_fv"
490 return 0
491 fi
492 _ffi="$(_math "$_ffi" - 1)"
493 done
494
495 printf -- "%s" "$_str"
496
497 }
498
499 _exists() {
500 cmd="$1"
501 if [ -z "$cmd" ]; then
502 _usage "Usage: _exists cmd"
503 return 1
504 fi
505
506 if eval type type >/dev/null 2>&1; then
507 eval type "$cmd" >/dev/null 2>&1
508 elif command >/dev/null 2>&1; then
509 command -v "$cmd" >/dev/null 2>&1
510 else
511 which "$cmd" >/dev/null 2>&1
512 fi
513 ret="$?"
514 _debug3 "$cmd exists=$ret"
515 return $ret
516 }
517
518 #a + b
519 _math() {
520 _m_opts="$@"
521 printf "%s" "$(($_m_opts))"
522 }
523
524 _h_char_2_dec() {
525 _ch=$1
526 case "${_ch}" in
527 a | A)
528 printf "10"
529 ;;
530 b | B)
531 printf "11"
532 ;;
533 c | C)
534 printf "12"
535 ;;
536 d | D)
537 printf "13"
538 ;;
539 e | E)
540 printf "14"
541 ;;
542 f | F)
543 printf "15"
544 ;;
545 *)
546 printf "%s" "$_ch"
547 ;;
548 esac
549
550 }
551
552 _URGLY_PRINTF=""
553 if [ "$(printf '\x41')" != 'A' ]; then
554 _URGLY_PRINTF=1
555 fi
556
557 _ESCAPE_XARGS=""
558 if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
559 _ESCAPE_XARGS=1
560 fi
561
562 _h2b() {
563 if _exists xxd && xxd -r -p 2>/dev/null; then
564 return
565 fi
566
567 hex=$(cat)
568 ic=""
569 jc=""
570 _debug2 _URGLY_PRINTF "$_URGLY_PRINTF"
571 if [ -z "$_URGLY_PRINTF" ]; then
572 if [ "$_ESCAPE_XARGS" ] && _exists xargs; then
573 _debug2 "xargs"
574 echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf
575 else
576 for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do
577 if [ -z "$h" ]; then
578 break
579 fi
580 printf "\x$h%s"
581 done
582 fi
583 else
584 for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do
585 if [ -z "$ic" ]; then
586 ic=$c
587 continue
588 fi
589 jc=$c
590 ic="$(_h_char_2_dec "$ic")"
591 jc="$(_h_char_2_dec "$jc")"
592 printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s"
593 ic=""
594 jc=""
595 done
596 fi
597
598 }
599
600 _is_solaris() {
601 _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS"
602 }
603
604 #_ascii_hex str
605 #this can only process ascii chars, should only be used when od command is missing as a backup way.
606 _ascii_hex() {
607 _debug2 "Using _ascii_hex"
608 _str="$1"
609 _str_len=${#_str}
610 _h_i=1
611 while [ "$_h_i" -le "$_str_len" ]; do
612 _str_c="$(printf "%s" "$_str" | cut -c "$_h_i")"
613 printf " %02x" "'$_str_c"
614 _h_i="$(_math "$_h_i" + 1)"
615 done
616 }
617
618 #stdin output hexstr splited by one space
619 #input:"abc"
620 #output: " 61 62 63"
621 _hex_dump() {
622 if _exists od; then
623 od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n"
624 elif _exists hexdump; then
625 _debug3 "using hexdump"
626 hexdump -v -e '/1 ""' -e '/1 " %02x" ""'
627 elif _exists xxd; then
628 _debug3 "using xxd"
629 xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " "
630 else
631 _debug3 "using _ascii_hex"
632 str=$(cat)
633 _ascii_hex "$str"
634 fi
635 }
636
637 #url encode, no-preserved chars
638 #A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
639 #41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a
640
641 #a b c d e f g h i j k l m n o p q r s t u v w x y z
642 #61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a
643
644 #0 1 2 3 4 5 6 7 8 9 - _ . ~
645 #30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e
646
647 #stdin stdout
648 _url_encode() {
649 _hex_str=$(_hex_dump)
650 _debug3 "_url_encode"
651 _debug3 "_hex_str" "$_hex_str"
652 for _hex_code in $_hex_str; do
653 #upper case
654 case "${_hex_code}" in
655 "41")
656 printf "%s" "A"
657 ;;
658 "42")
659 printf "%s" "B"
660 ;;
661 "43")
662 printf "%s" "C"
663 ;;
664 "44")
665 printf "%s" "D"
666 ;;
667 "45")
668 printf "%s" "E"
669 ;;
670 "46")
671 printf "%s" "F"
672 ;;
673 "47")
674 printf "%s" "G"
675 ;;
676 "48")
677 printf "%s" "H"
678 ;;
679 "49")
680 printf "%s" "I"
681 ;;
682 "4a")
683 printf "%s" "J"
684 ;;
685 "4b")
686 printf "%s" "K"
687 ;;
688 "4c")
689 printf "%s" "L"
690 ;;
691 "4d")
692 printf "%s" "M"
693 ;;
694 "4e")
695 printf "%s" "N"
696 ;;
697 "4f")
698 printf "%s" "O"
699 ;;
700 "50")
701 printf "%s" "P"
702 ;;
703 "51")
704 printf "%s" "Q"
705 ;;
706 "52")
707 printf "%s" "R"
708 ;;
709 "53")
710 printf "%s" "S"
711 ;;
712 "54")
713 printf "%s" "T"
714 ;;
715 "55")
716 printf "%s" "U"
717 ;;
718 "56")
719 printf "%s" "V"
720 ;;
721 "57")
722 printf "%s" "W"
723 ;;
724 "58")
725 printf "%s" "X"
726 ;;
727 "59")
728 printf "%s" "Y"
729 ;;
730 "5a")
731 printf "%s" "Z"
732 ;;
733
734 #lower case
735 "61")
736 printf "%s" "a"
737 ;;
738 "62")
739 printf "%s" "b"
740 ;;
741 "63")
742 printf "%s" "c"
743 ;;
744 "64")
745 printf "%s" "d"
746 ;;
747 "65")
748 printf "%s" "e"
749 ;;
750 "66")
751 printf "%s" "f"
752 ;;
753 "67")
754 printf "%s" "g"
755 ;;
756 "68")
757 printf "%s" "h"
758 ;;
759 "69")
760 printf "%s" "i"
761 ;;
762 "6a")
763 printf "%s" "j"
764 ;;
765 "6b")
766 printf "%s" "k"
767 ;;
768 "6c")
769 printf "%s" "l"
770 ;;
771 "6d")
772 printf "%s" "m"
773 ;;
774 "6e")
775 printf "%s" "n"
776 ;;
777 "6f")
778 printf "%s" "o"
779 ;;
780 "70")
781 printf "%s" "p"
782 ;;
783 "71")
784 printf "%s" "q"
785 ;;
786 "72")
787 printf "%s" "r"
788 ;;
789 "73")
790 printf "%s" "s"
791 ;;
792 "74")
793 printf "%s" "t"
794 ;;
795 "75")
796 printf "%s" "u"
797 ;;
798 "76")
799 printf "%s" "v"
800 ;;
801 "77")
802 printf "%s" "w"
803 ;;
804 "78")
805 printf "%s" "x"
806 ;;
807 "79")
808 printf "%s" "y"
809 ;;
810 "7a")
811 printf "%s" "z"
812 ;;
813 #numbers
814 "30")
815 printf "%s" "0"
816 ;;
817 "31")
818 printf "%s" "1"
819 ;;
820 "32")
821 printf "%s" "2"
822 ;;
823 "33")
824 printf "%s" "3"
825 ;;
826 "34")
827 printf "%s" "4"
828 ;;
829 "35")
830 printf "%s" "5"
831 ;;
832 "36")
833 printf "%s" "6"
834 ;;
835 "37")
836 printf "%s" "7"
837 ;;
838 "38")
839 printf "%s" "8"
840 ;;
841 "39")
842 printf "%s" "9"
843 ;;
844 "2d")
845 printf "%s" "-"
846 ;;
847 "5f")
848 printf "%s" "_"
849 ;;
850 "2e")
851 printf "%s" "."
852 ;;
853 "7e")
854 printf "%s" "~"
855 ;;
856 #other hex
857 *)
858 printf '%%%s' "$_hex_code"
859 ;;
860 esac
861 done
862 }
863
864 _json_encode() {
865 _j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")"
866 _debug3 "_json_encode"
867 _debug3 "_j_str" "$_j_str"
868 echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
869 }
870
871 #from: http:\/\/ to http://
872 _json_decode() {
873 _j_str="$(sed 's#\\/#/#g')"
874 _debug3 "_json_decode"
875 _debug3 "_j_str" "$_j_str"
876 echo "$_j_str"
877 }
878
879 #options file
880 _sed_i() {
881 options="$1"
882 filename="$2"
883 if [ -z "$filename" ]; then
884 _usage "Usage:_sed_i options filename"
885 return 1
886 fi
887 _debug2 options "$options"
888 if sed -h 2>&1 | grep "\-i\[SUFFIX]" >/dev/null 2>&1; then
889 _debug "Using sed -i"
890 sed -i "$options" "$filename"
891 else
892 _debug "No -i support in sed"
893 text="$(cat "$filename")"
894 echo "$text" | sed "$options" >"$filename"
895 fi
896 }
897
898 _egrep_o() {
899 if ! egrep -o "$1" 2>/dev/null; then
900 sed -n 's/.*\('"$1"'\).*/\1/p'
901 fi
902 }
903
904 #Usage: file startline endline
905 _getfile() {
906 filename="$1"
907 startline="$2"
908 endline="$3"
909 if [ -z "$endline" ]; then
910 _usage "Usage: file startline endline"
911 return 1
912 fi
913
914 i="$(grep -n -- "$startline" "$filename" | cut -d : -f 1)"
915 if [ -z "$i" ]; then
916 _err "Can not find start line: $startline"
917 return 1
918 fi
919 i="$(_math "$i" + 1)"
920 _debug i "$i"
921
922 j="$(grep -n -- "$endline" "$filename" | cut -d : -f 1)"
923 if [ -z "$j" ]; then
924 _err "Can not find end line: $endline"
925 return 1
926 fi
927 j="$(_math "$j" - 1)"
928 _debug j "$j"
929
930 sed -n "$i,${j}p" "$filename"
931
932 }
933
934 #Usage: multiline
935 _base64() {
936 [ "" ] #urgly
937 if [ "$1" ]; then
938 _debug3 "base64 multiline:'$1'"
939 ${ACME_OPENSSL_BIN:-openssl} base64 -e
940 else
941 _debug3 "base64 single line."
942 ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
943 fi
944 }
945
946 #Usage: multiline
947 _dbase64() {
948 if [ "$1" ]; then
949 ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
950 else
951 ${ACME_OPENSSL_BIN:-openssl} base64 -d
952 fi
953 }
954
955 #file
956 _checkcert() {
957 _cf="$1"
958 if [ "$DEBUG" ]; then
959 openssl x509 -noout -text -in "$_cf"
960 else
961 openssl x509 -noout -text -in "$_cf" >/dev/null 2>&1
962 fi
963 }
964
965 #Usage: hashalg [outputhex]
966 #Output Base64-encoded digest
967 _digest() {
968 alg="$1"
969 if [ -z "$alg" ]; then
970 _usage "Usage: _digest hashalg"
971 return 1
972 fi
973
974 outputhex="$2"
975
976 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
977 if [ "$outputhex" ]; then
978 ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
979 else
980 ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
981 fi
982 else
983 _err "$alg is not supported yet"
984 return 1
985 fi
986
987 }
988
989 #Usage: hashalg secret_hex [outputhex]
990 #Output binary hmac
991 _hmac() {
992 alg="$1"
993 secret_hex="$2"
994 outputhex="$3"
995
996 if [ -z "$secret_hex" ]; then
997 _usage "Usage: _hmac hashalg secret [outputhex]"
998 return 1
999 fi
1000
1001 if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
1002 if [ "$outputhex" ]; then
1003 (${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
1004 else
1005 ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
1006 fi
1007 else
1008 _err "$alg is not supported yet"
1009 return 1
1010 fi
1011
1012 }
1013
1014 #Usage: keyfile hashalg
1015 #Output: Base64-encoded signature value
1016 _sign() {
1017 keyfile="$1"
1018 alg="$2"
1019 if [ -z "$alg" ]; then
1020 _usage "Usage: _sign keyfile hashalg"
1021 return 1
1022 fi
1023
1024 _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
1025
1026 if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || grep "BEGIN PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
1027 $_sign_openssl -$alg | _base64
1028 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
1029 if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
1030 _err "Sign failed: $_sign_openssl"
1031 _err "Key file: $keyfile"
1032 _err "Key content:$(wc -l <"$keyfile") lines"
1033 return 1
1034 fi
1035 _debug3 "_signedECText" "$_signedECText"
1036 _ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
1037 _ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
1038 if [ "$__ECC_KEY_LEN" -eq "256" ]; then
1039 while [ "${#_ec_r}" -lt "64" ]; do
1040 _ec_r="0${_ec_r}"
1041 done
1042 while [ "${#_ec_s}" -lt "64" ]; do
1043 _ec_s="0${_ec_s}"
1044 done
1045 fi
1046 if [ "$__ECC_KEY_LEN" -eq "384" ]; then
1047 while [ "${#_ec_r}" -lt "96" ]; do
1048 _ec_r="0${_ec_r}"
1049 done
1050 while [ "${#_ec_s}" -lt "96" ]; do
1051 _ec_s="0${_ec_s}"
1052 done
1053 fi
1054 if [ "$__ECC_KEY_LEN" -eq "512" ]; then
1055 while [ "${#_ec_r}" -lt "132" ]; do
1056 _ec_r="0${_ec_r}"
1057 done
1058 while [ "${#_ec_s}" -lt "132" ]; do
1059 _ec_s="0${_ec_s}"
1060 done
1061 fi
1062 _debug3 "_ec_r" "$_ec_r"
1063 _debug3 "_ec_s" "$_ec_s"
1064 printf "%s" "$_ec_r$_ec_s" | _h2b | _base64
1065 else
1066 _err "Unknown key file format."
1067 return 1
1068 fi
1069
1070 }
1071
1072 #keylength or isEcc flag (empty str => not ecc)
1073 _isEccKey() {
1074 _length="$1"
1075
1076 if [ -z "$_length" ]; then
1077 return 1
1078 fi
1079
1080 [ "$_length" != "1024" ] &&
1081 [ "$_length" != "2048" ] &&
1082 [ "$_length" != "3072" ] &&
1083 [ "$_length" != "4096" ] &&
1084 [ "$_length" != "8192" ]
1085 }
1086
1087 # _createkey 2048|ec-256 file
1088 _createkey() {
1089 length="$1"
1090 f="$2"
1091 _debug2 "_createkey for file:$f"
1092 eccname="$length"
1093 if _startswith "$length" "ec-"; then
1094 length=$(printf "%s" "$length" | cut -d '-' -f 2-100)
1095
1096 if [ "$length" = "256" ]; then
1097 eccname="prime256v1"
1098 fi
1099 if [ "$length" = "384" ]; then
1100 eccname="secp384r1"
1101 fi
1102 if [ "$length" = "521" ]; then
1103 eccname="secp521r1"
1104 fi
1105
1106 fi
1107
1108 if [ -z "$length" ]; then
1109 length=2048
1110 fi
1111
1112 _debug "Use length $length"
1113
1114 if ! touch "$f" >/dev/null 2>&1; then
1115 _f_path="$(dirname "$f")"
1116 _debug _f_path "$_f_path"
1117 if ! mkdir -p "$_f_path"; then
1118 _err "Can not create path: $_f_path"
1119 return 1
1120 fi
1121 fi
1122
1123 if _isEccKey "$length"; then
1124 _debug "Using ec name: $eccname"
1125 if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -genkey 2>/dev/null)"; then
1126 echo "$_opkey" >"$f"
1127 else
1128 _err "error ecc key name: $eccname"
1129 return 1
1130 fi
1131 else
1132 _debug "Using RSA: $length"
1133 if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa "$length" 2>/dev/null)"; then
1134 echo "$_opkey" >"$f"
1135 else
1136 _err "error rsa key: $length"
1137 return 1
1138 fi
1139 fi
1140
1141 if [ "$?" != "0" ]; then
1142 _err "Create key error."
1143 return 1
1144 fi
1145 }
1146
1147 #domain
1148 _is_idn() {
1149 _is_idn_d="$1"
1150 _debug2 _is_idn_d "$_is_idn_d"
1151 _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
1152 _debug2 _idn_temp "$_idn_temp"
1153 [ "$_idn_temp" ]
1154 }
1155
1156 #aa.com
1157 #aa.com,bb.com,cc.com
1158 _idn() {
1159 __idn_d="$1"
1160 if ! _is_idn "$__idn_d"; then
1161 printf "%s" "$__idn_d"
1162 return 0
1163 fi
1164
1165 if _exists idn; then
1166 if _contains "$__idn_d" ','; then
1167 _i_first="1"
1168 for f in $(echo "$__idn_d" | tr ',' ' '); do
1169 [ -z "$f" ] && continue
1170 if [ -z "$_i_first" ]; then
1171 printf "%s" ","
1172 else
1173 _i_first=""
1174 fi
1175 idn --quiet "$f" | tr -d "\r\n"
1176 done
1177 else
1178 idn "$__idn_d" | tr -d "\r\n"
1179 fi
1180 else
1181 _err "Please install idn to process IDN names."
1182 fi
1183 }
1184
1185 #_createcsr cn san_list keyfile csrfile conf acmeValidationv1
1186 _createcsr() {
1187 _debug _createcsr
1188 domain="$1"
1189 domainlist="$2"
1190 csrkey="$3"
1191 csr="$4"
1192 csrconf="$5"
1193 acmeValidationv1="$6"
1194 _debug2 domain "$domain"
1195 _debug2 domainlist "$domainlist"
1196 _debug2 csrkey "$csrkey"
1197 _debug2 csr "$csr"
1198 _debug2 csrconf "$csrconf"
1199
1200 printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf"
1201
1202 if [ "$acmeValidationv1" ]; then
1203 domainlist="$(_idn "$domainlist")"
1204 printf -- "\nsubjectAltName=DNS:$domainlist" >>"$csrconf"
1205 elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then
1206 #single domain
1207 _info "Single domain" "$domain"
1208 printf -- "\nsubjectAltName=DNS:$(_idn "$domain")" >>"$csrconf"
1209 else
1210 domainlist="$(_idn "$domainlist")"
1211 _debug2 domainlist "$domainlist"
1212 if _contains "$domainlist" ","; then
1213 alt="DNS:$(_idn "$domain"),DNS:$(echo "$domainlist" | sed "s/,,/,/g" | sed "s/,/,DNS:/g")"
1214 else
1215 alt="DNS:$(_idn "$domain"),DNS:$domainlist"
1216 fi
1217 #multi
1218 _info "Multi domain" "$alt"
1219 printf -- "\nsubjectAltName=$alt" >>"$csrconf"
1220 fi
1221 if [ "$Le_OCSP_Staple" = "1" ]; then
1222 _savedomainconf Le_OCSP_Staple "$Le_OCSP_Staple"
1223 printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf"
1224 fi
1225
1226 if [ "$acmeValidationv1" ]; then
1227 printf "\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}"
1228 fi
1229
1230 _csr_cn="$(_idn "$domain")"
1231 _debug2 _csr_cn "$_csr_cn"
1232 if _contains "$(uname -a)" "MINGW"; then
1233 ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
1234 else
1235 ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
1236 fi
1237 }
1238
1239 #_signcsr key csr conf cert
1240 _signcsr() {
1241 key="$1"
1242 csr="$2"
1243 conf="$3"
1244 cert="$4"
1245 _debug "_signcsr"
1246
1247 _msg="$(${ACME_OPENSSL_BIN:-openssl} x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)"
1248 _ret="$?"
1249 _debug "$_msg"
1250 return $_ret
1251 }
1252
1253 #_csrfile
1254 _readSubjectFromCSR() {
1255 _csrfile="$1"
1256 if [ -z "$_csrfile" ]; then
1257 _usage "_readSubjectFromCSR mycsr.csr"
1258 return 1
1259 fi
1260 ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n'
1261 }
1262
1263 #_csrfile
1264 #echo comma separated domain list
1265 _readSubjectAltNamesFromCSR() {
1266 _csrfile="$1"
1267 if [ -z "$_csrfile" ]; then
1268 _usage "_readSubjectAltNamesFromCSR mycsr.csr"
1269 return 1
1270 fi
1271
1272 _csrsubj="$(_readSubjectFromCSR "$_csrfile")"
1273 _debug _csrsubj "$_csrsubj"
1274
1275 _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
1276 _debug _dnsAltnames "$_dnsAltnames"
1277
1278 if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
1279 _debug "AltNames contains subject"
1280 _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')"
1281 _debug _excapedAlgnames "$_excapedAlgnames"
1282 _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')"
1283 _debug _escapedSubject "$_escapedSubject"
1284 _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")"
1285 _debug _dnsAltnames "$_dnsAltnames"
1286 else
1287 _debug "AltNames doesn't contain subject"
1288 fi
1289
1290 echo "$_dnsAltnames" | sed "s/DNS://g"
1291 }
1292
1293 #_csrfile
1294 _readKeyLengthFromCSR() {
1295 _csrfile="$1"
1296 if [ -z "$_csrfile" ]; then
1297 _usage "_readKeyLengthFromCSR mycsr.csr"
1298 return 1
1299 fi
1300
1301 _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
1302 _debug2 _outcsr "$_outcsr"
1303 if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
1304 _debug "ECC CSR"
1305 echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
1306 else
1307 _debug "RSA CSR"
1308 _rkl="$(echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1)"
1309 if [ "$_rkl" ]; then
1310 echo "$_rkl"
1311 else
1312 echo "$_outcsr" | tr "\t" " " | _egrep_o "RSA Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
1313 fi
1314 fi
1315 }
1316
1317 _ss() {
1318 _port="$1"
1319
1320 if _exists "ss"; then
1321 _debug "Using: ss"
1322 ss -ntpl 2>/dev/null | grep ":$_port "
1323 return 0
1324 fi
1325
1326 if _exists "netstat"; then
1327 _debug "Using: netstat"
1328 if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then
1329 #for windows version netstat tool
1330 netstat -an -p tcp | grep "LISTENING" | grep ":$_port "
1331 else
1332 if netstat -help 2>&1 | grep "\-p protocol" >/dev/null; then
1333 netstat -an -p tcp | grep LISTEN | grep ":$_port "
1334 elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then
1335 #for solaris
1336 netstat -an -P tcp | grep "\.$_port " | grep "LISTEN"
1337 elif netstat -help 2>&1 | grep "\-p" >/dev/null; then
1338 #for full linux
1339 netstat -ntpl | grep ":$_port "
1340 else
1341 #for busybox (embedded linux; no pid support)
1342 netstat -ntl 2>/dev/null | grep ":$_port "
1343 fi
1344 fi
1345 return 0
1346 fi
1347
1348 return 1
1349 }
1350
1351 #outfile key cert cacert [password [name [caname]]]
1352 _toPkcs() {
1353 _cpfx="$1"
1354 _ckey="$2"
1355 _ccert="$3"
1356 _cca="$4"
1357 pfxPassword="$5"
1358 pfxName="$6"
1359 pfxCaname="$7"
1360
1361 if [ "$pfxCaname" ]; then
1362 ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" -caname "$pfxCaname"
1363 elif [ "$pfxName" ]; then
1364 ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName"
1365 elif [ "$pfxPassword" ]; then
1366 ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword"
1367 else
1368 ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca"
1369 fi
1370
1371 }
1372
1373 #domain [password] [isEcc]
1374 toPkcs() {
1375 domain="$1"
1376 pfxPassword="$2"
1377 if [ -z "$domain" ]; then
1378 _usage "Usage: $PROJECT_ENTRY --toPkcs -d domain [--password pfx-password]"
1379 return 1
1380 fi
1381
1382 _isEcc="$3"
1383
1384 _initpath "$domain" "$_isEcc"
1385
1386 _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword"
1387
1388 if [ "$?" = "0" ]; then
1389 _info "Success, Pfx is exported to: $CERT_PFX_PATH"
1390 fi
1391
1392 }
1393
1394 #domain [isEcc]
1395 toPkcs8() {
1396 domain="$1"
1397
1398 if [ -z "$domain" ]; then
1399 _usage "Usage: $PROJECT_ENTRY --toPkcs8 -d domain [--ecc]"
1400 return 1
1401 fi
1402
1403 _isEcc="$2"
1404
1405 _initpath "$domain" "$_isEcc"
1406
1407 ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
1408
1409 if [ "$?" = "0" ]; then
1410 _info "Success, $CERT_PKCS8_PATH"
1411 fi
1412
1413 }
1414
1415 #[2048]
1416 createAccountKey() {
1417 _info "Creating account key"
1418 if [ -z "$1" ]; then
1419 _usage "Usage: $PROJECT_ENTRY --createAccountKey --accountkeylength 2048"
1420 return
1421 fi
1422
1423 length=$1
1424 _create_account_key "$length"
1425
1426 }
1427
1428 _create_account_key() {
1429
1430 length=$1
1431
1432 if [ -z "$length" ] || [ "$length" = "$NO_VALUE" ]; then
1433 _debug "Use default length $DEFAULT_ACCOUNT_KEY_LENGTH"
1434 length="$DEFAULT_ACCOUNT_KEY_LENGTH"
1435 fi
1436
1437 _debug length "$length"
1438 _initpath
1439
1440 mkdir -p "$CA_DIR"
1441 if [ -s "$ACCOUNT_KEY_PATH" ]; then
1442 _info "Account key exists, skip"
1443 return 0
1444 else
1445 #generate account key
1446 if _createkey "$length" "$ACCOUNT_KEY_PATH"; then
1447 chmod 600 "$ACCOUNT_KEY_PATH"
1448 _info "Create account key ok."
1449 return 0
1450 else
1451 _err "Create account key error."
1452 return 1
1453 fi
1454 fi
1455
1456 }
1457
1458 #domain [length]
1459 createDomainKey() {
1460 _info "Creating domain key"
1461 if [ -z "$1" ]; then
1462 _usage "Usage: $PROJECT_ENTRY --createDomainKey -d domain.com [ --keylength 2048 ]"
1463 return
1464 fi
1465
1466 domain=$1
1467 _cdl=$2
1468
1469 if [ -z "$_cdl" ]; then
1470 _debug "Use DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH"
1471 _cdl="$DEFAULT_DOMAIN_KEY_LENGTH"
1472 fi
1473
1474 _initpath "$domain" "$_cdl"
1475
1476 if [ ! -f "$CERT_KEY_PATH" ] || [ ! -s "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$_ACME_IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then
1477 if _createkey "$_cdl" "$CERT_KEY_PATH"; then
1478 _savedomainconf Le_Keylength "$_cdl"
1479 _info "The domain key is here: $(__green $CERT_KEY_PATH)"
1480 return 0
1481 else
1482 _err "Can not create domain key"
1483 return 1
1484 fi
1485 else
1486 if [ "$_ACME_IS_RENEW" ]; then
1487 _info "Domain key exists, skip"
1488 return 0
1489 else
1490 _err "Domain key exists, do you want to overwrite the key?"
1491 _err "Add '--force', and try again."
1492 return 1
1493 fi
1494 fi
1495
1496 }
1497
1498 # domain domainlist isEcc
1499 createCSR() {
1500 _info "Creating csr"
1501 if [ -z "$1" ]; then
1502 _usage "Usage: $PROJECT_ENTRY --createCSR -d domain1.com [-d domain2.com -d domain3.com ... ]"
1503 return
1504 fi
1505
1506 domain="$1"
1507 domainlist="$2"
1508 _isEcc="$3"
1509
1510 _initpath "$domain" "$_isEcc"
1511
1512 if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then
1513 _info "CSR exists, skip"
1514 return
1515 fi
1516
1517 if [ ! -f "$CERT_KEY_PATH" ]; then
1518 _err "The key file is not found: $CERT_KEY_PATH"
1519 _err "Please create the key file first."
1520 return 1
1521 fi
1522 _createcsr "$domain" "$domainlist" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"
1523
1524 }
1525
1526 _url_replace() {
1527 tr '/+' '_-' | tr -d '= '
1528 }
1529
1530 #base64 string
1531 _durl_replace_base64() {
1532 _l=$((${#1} % 4))
1533 if [ $_l -eq 2 ]; then
1534 _s="$1"'=='
1535 elif [ $_l -eq 3 ]; then
1536 _s="$1"'='
1537 else
1538 _s="$1"
1539 fi
1540 echo "$_s" | tr '_-' '/+'
1541 }
1542
1543 _time2str() {
1544 #BSD
1545 if date -u -r "$1" 2>/dev/null; then
1546 return
1547 fi
1548
1549 #Linux
1550 if date -u -d@"$1" 2>/dev/null; then
1551 return
1552 fi
1553
1554 #Solaris
1555 if _exists adb; then
1556 _t_s_a=$(echo "0t${1}=Y" | adb)
1557 echo "$_t_s_a"
1558 fi
1559
1560 #Busybox
1561 if echo "$1" | awk '{ print strftime("%c", $0); }' 2>/dev/null; then
1562 return
1563 fi
1564 }
1565
1566 _normalizeJson() {
1567 sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n"
1568 }
1569
1570 _stat() {
1571 #Linux
1572 if stat -c '%U:%G' "$1" 2>/dev/null; then
1573 return
1574 fi
1575
1576 #BSD
1577 if stat -f '%Su:%Sg' "$1" 2>/dev/null; then
1578 return
1579 fi
1580
1581 return 1 #error, 'stat' not found
1582 }
1583
1584 #keyfile
1585 _calcjwk() {
1586 keyfile="$1"
1587 if [ -z "$keyfile" ]; then
1588 _usage "Usage: _calcjwk keyfile"
1589 return 1
1590 fi
1591
1592 if [ "$JWK_HEADER" ] && [ "$__CACHED_JWK_KEY_FILE" = "$keyfile" ]; then
1593 _debug2 "Use cached jwk for file: $__CACHED_JWK_KEY_FILE"
1594 return 0
1595 fi
1596
1597 if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
1598 _debug "RSA key"
1599 pub_exp=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)
1600 if [ "${#pub_exp}" = "5" ]; then
1601 pub_exp=0$pub_exp
1602 fi
1603 _debug3 pub_exp "$pub_exp"
1604
1605 e=$(echo "$pub_exp" | _h2b | _base64)
1606 _debug3 e "$e"
1607
1608 modulus=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2)
1609 _debug3 modulus "$modulus"
1610 n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)"
1611 _debug3 n "$n"
1612
1613 jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
1614 _debug3 jwk "$jwk"
1615
1616 JWK_HEADER='{"alg": "RS256", "jwk": '$jwk'}'
1617 JWK_HEADERPLACE_PART1='{"nonce": "'
1618 JWK_HEADERPLACE_PART2='", "alg": "RS256"'
1619 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
1620 _debug "EC key"
1621 crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
1622 _debug3 crv "$crv"
1623 __ECC_KEY_LEN=$(echo "$crv" | cut -d "-" -f 2)
1624 if [ "$__ECC_KEY_LEN" = "521" ]; then
1625 __ECC_KEY_LEN=512
1626 fi
1627 _debug3 __ECC_KEY_LEN "$__ECC_KEY_LEN"
1628 if [ -z "$crv" ]; then
1629 _debug "Let's try ASN1 OID"
1630 crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
1631 _debug3 crv_oid "$crv_oid"
1632 case "${crv_oid}" in
1633 "prime256v1")
1634 crv="P-256"
1635 __ECC_KEY_LEN=256
1636 ;;
1637 "secp384r1")
1638 crv="P-384"
1639 __ECC_KEY_LEN=384
1640 ;;
1641 "secp521r1")
1642 crv="P-521"
1643 __ECC_KEY_LEN=512
1644 ;;
1645 *)
1646 _err "ECC oid : $crv_oid"
1647 return 1
1648 ;;
1649 esac
1650 _debug3 crv "$crv"
1651 fi
1652
1653 pubi="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
1654 pubi=$(_math "$pubi" + 1)
1655 _debug3 pubi "$pubi"
1656
1657 pubj="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
1658 pubj=$(_math "$pubj" - 1)
1659 _debug3 pubj "$pubj"
1660
1661 pubtext="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
1662 _debug3 pubtext "$pubtext"
1663
1664 xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)"
1665 xlen=$(_math "$xlen" / 4)
1666 _debug3 xlen "$xlen"
1667
1668 xend=$(_math "$xlen" + 1)
1669 x="$(printf "%s" "$pubtext" | cut -d : -f 2-"$xend")"
1670 _debug3 x "$x"
1671
1672 x64="$(printf "%s" "$x" | tr -d : | _h2b | _base64 | _url_replace)"
1673 _debug3 x64 "$x64"
1674
1675 xend=$(_math "$xend" + 1)
1676 y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-10000)"
1677 _debug3 y "$y"
1678
1679 y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)"
1680 _debug3 y64 "$y64"
1681
1682 jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}'
1683 _debug3 jwk "$jwk"
1684
1685 JWK_HEADER='{"alg": "ES'$__ECC_KEY_LEN'", "jwk": '$jwk'}'
1686 JWK_HEADERPLACE_PART1='{"nonce": "'
1687 JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"'
1688 else
1689 _err "Only RSA or EC key is supported. keyfile=$keyfile"
1690 _debug2 "$(cat "$keyfile")"
1691 return 1
1692 fi
1693
1694 _debug3 JWK_HEADER "$JWK_HEADER"
1695 __CACHED_JWK_KEY_FILE="$keyfile"
1696 }
1697
1698 _time() {
1699 date -u "+%s"
1700 }
1701
1702 _utc_date() {
1703 date -u "+%Y-%m-%d %H:%M:%S"
1704 }
1705
1706 _mktemp() {
1707 if _exists mktemp; then
1708 if mktemp 2>/dev/null; then
1709 return 0
1710 elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then
1711 #for Mac osx
1712 return 0
1713 fi
1714 fi
1715 if [ -d "/tmp" ]; then
1716 echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp"
1717 return 0
1718 elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then
1719 echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp"
1720 return 0
1721 fi
1722 _err "Can not create temp file."
1723 }
1724
1725 _inithttp() {
1726
1727 if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then
1728 HTTP_HEADER="$(_mktemp)"
1729 _debug2 HTTP_HEADER "$HTTP_HEADER"
1730 fi
1731
1732 if [ "$__HTTP_INITIALIZED" ]; then
1733 if [ "$_ACME_CURL$_ACME_WGET" ]; then
1734 _debug2 "Http already initialized."
1735 return 0
1736 fi
1737 fi
1738
1739 if [ -z "$_ACME_CURL" ] && _exists "curl"; then
1740 _ACME_CURL="curl -L --silent --dump-header $HTTP_HEADER "
1741 if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
1742 _CURL_DUMP="$(_mktemp)"
1743 _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
1744 fi
1745
1746 if [ "$CA_PATH" ]; then
1747 _ACME_CURL="$_ACME_CURL --capath $CA_PATH "
1748 elif [ "$CA_BUNDLE" ]; then
1749 _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
1750 fi
1751
1752 if _contains "$(curl --help 2>&1)" "--globoff"; then
1753 _ACME_CURL="$_ACME_CURL -g "
1754 fi
1755 fi
1756
1757 if [ -z "$_ACME_WGET" ] && _exists "wget"; then
1758 _ACME_WGET="wget -q"
1759 if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
1760 _ACME_WGET="$_ACME_WGET -d "
1761 fi
1762 if [ "$CA_PATH" ]; then
1763 _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH "
1764 elif [ "$CA_BUNDLE" ]; then
1765 _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
1766 fi
1767 fi
1768
1769 #from wget 1.14: do not skip body on 404 error
1770 if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--content-on-error"; then
1771 _ACME_WGET="$_ACME_WGET --content-on-error "
1772 fi
1773
1774 __HTTP_INITIALIZED=1
1775
1776 }
1777
1778 # body url [needbase64] [POST|PUT|DELETE] [ContentType]
1779 _post() {
1780 body="$1"
1781 _post_url="$2"
1782 needbase64="$3"
1783 httpmethod="$4"
1784 _postContentType="$5"
1785
1786 if [ -z "$httpmethod" ]; then
1787 httpmethod="POST"
1788 fi
1789 _debug $httpmethod
1790 _debug "_post_url" "$_post_url"
1791 _debug2 "body" "$body"
1792 _debug2 "_postContentType" "$_postContentType"
1793
1794 _inithttp
1795
1796 if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
1797 _CURL="$_ACME_CURL"
1798 if [ "$HTTPS_INSECURE" ]; then
1799 _CURL="$_CURL --insecure "
1800 fi
1801 if [ "$httpmethod" = "HEAD" ]; then
1802 _CURL="$_CURL -I "
1803 fi
1804 _debug "_CURL" "$_CURL"
1805 if [ "$needbase64" ]; then
1806 if [ "$body" ]; then
1807 if [ "$_postContentType" ]; then
1808 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)"
1809 else
1810 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)"
1811 fi
1812 else
1813 if [ "$_postContentType" ]; then
1814 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)"
1815 else
1816 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)"
1817 fi
1818 fi
1819 else
1820 if [ "$body" ]; then
1821 if [ "$_postContentType" ]; then
1822 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")"
1823 else
1824 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")"
1825 fi
1826 else
1827 if [ "$_postContentType" ]; then
1828 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
1829 else
1830 response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")"
1831 fi
1832 fi
1833 fi
1834 _ret="$?"
1835 if [ "$_ret" != "0" ]; then
1836 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
1837 if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
1838 _err "Here is the curl dump log:"
1839 _err "$(cat "$_CURL_DUMP")"
1840 fi
1841 fi
1842 elif [ "$_ACME_WGET" ]; then
1843 _WGET="$_ACME_WGET"
1844 if [ "$HTTPS_INSECURE" ]; then
1845 _WGET="$_WGET --no-check-certificate "
1846 fi
1847 if [ "$httpmethod" = "HEAD" ]; then
1848 _WGET="$_WGET --read-timeout=3.0 --tries=2 "
1849 fi
1850 _debug "_WGET" "$_WGET"
1851 if [ "$needbase64" ]; then
1852 if [ "$httpmethod" = "POST" ]; then
1853 if [ "$_postContentType" ]; then
1854 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
1855 else
1856 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
1857 fi
1858 else
1859 if [ "$_postContentType" ]; then
1860 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
1861 else
1862 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)"
1863 fi
1864 fi
1865 else
1866 if [ "$httpmethod" = "POST" ]; then
1867 if [ "$_postContentType" ]; then
1868 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1869 else
1870 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1871 fi
1872 elif [ "$httpmethod" = "HEAD" ]; then
1873 if [ "$_postContentType" ]; then
1874 response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1875 else
1876 response="$($_WGET --spider -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1877 fi
1878 else
1879 if [ "$_postContentType" ]; then
1880 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1881 else
1882 response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")"
1883 fi
1884 fi
1885 fi
1886 _ret="$?"
1887 if [ "$_ret" = "8" ]; then
1888 _ret=0
1889 _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
1890 fi
1891 if [ "$_ret" != "0" ]; then
1892 _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
1893 fi
1894 _sed_i "s/^ *//g" "$HTTP_HEADER"
1895 else
1896 _ret="$?"
1897 _err "Neither curl nor wget is found, can not do $httpmethod."
1898 fi
1899 _debug "_ret" "$_ret"
1900 printf "%s" "$response"
1901 return $_ret
1902 }
1903
1904 # url getheader timeout
1905 _get() {
1906 _debug GET
1907 url="$1"
1908 onlyheader="$2"
1909 t="$3"
1910 _debug url "$url"
1911 _debug "timeout=$t"
1912
1913 _inithttp
1914
1915 if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
1916 _CURL="$_ACME_CURL"
1917 if [ "$HTTPS_INSECURE" ]; then
1918 _CURL="$_CURL --insecure "
1919 fi
1920 if [ "$t" ]; then
1921 _CURL="$_CURL --connect-timeout $t"
1922 fi
1923 _debug "_CURL" "$_CURL"
1924 if [ "$onlyheader" ]; then
1925 $_CURL -I --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
1926 else
1927 $_CURL --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url"
1928 fi
1929 ret=$?
1930 if [ "$ret" != "0" ]; then
1931 _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
1932 if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
1933 _err "Here is the curl dump log:"
1934 _err "$(cat "$_CURL_DUMP")"
1935 fi
1936 fi
1937 elif [ "$_ACME_WGET" ]; then
1938 _WGET="$_ACME_WGET"
1939 if [ "$HTTPS_INSECURE" ]; then
1940 _WGET="$_WGET --no-check-certificate "
1941 fi
1942 if [ "$t" ]; then
1943 _WGET="$_WGET --timeout=$t"
1944 fi
1945 _debug "_WGET" "$_WGET"
1946 if [ "$onlyheader" ]; then
1947 $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1 | sed 's/^[ ]*//g'
1948 else
1949 $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -O - "$url"
1950 fi
1951 ret=$?
1952 if [ "$ret" = "8" ]; then
1953 ret=0
1954 _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
1955 fi
1956 if [ "$ret" != "0" ]; then
1957 _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
1958 fi
1959 else
1960 ret=$?
1961 _err "Neither curl nor wget is found, can not do GET."
1962 fi
1963 _debug "ret" "$ret"
1964 return $ret
1965 }
1966
1967 _head_n() {
1968 head -n "$1"
1969 }
1970
1971 _tail_n() {
1972 if ! tail -n "$1" 2>/dev/null; then
1973 #fix for solaris
1974 tail -"$1"
1975 fi
1976 }
1977
1978 # url payload needbase64 keyfile
1979 _send_signed_request() {
1980 url=$1
1981 payload=$2
1982 needbase64=$3
1983 keyfile=$4
1984 if [ -z "$keyfile" ]; then
1985 keyfile="$ACCOUNT_KEY_PATH"
1986 fi
1987 _debug url "$url"
1988 _debug payload "$payload"
1989
1990 if ! _calcjwk "$keyfile"; then
1991 return 1
1992 fi
1993
1994 __request_conent_type="$CONTENT_TYPE_JSON"
1995
1996 payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
1997 _debug3 payload64 "$payload64"
1998
1999 MAX_REQUEST_RETRY_TIMES=20
2000 _sleep_retry_sec=1
2001 _request_retry_times=0
2002 while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
2003 _request_retry_times=$(_math "$_request_retry_times" + 1)
2004 _debug3 _request_retry_times "$_request_retry_times"
2005 if [ -z "$_CACHED_NONCE" ]; then
2006 _headers=""
2007 if [ "$ACME_NEW_NONCE" ]; then
2008 _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
2009 nonceurl="$ACME_NEW_NONCE"
2010 if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type" >/dev/null; then
2011 _headers="$(cat "$HTTP_HEADER")"
2012 _debug2 _headers "$_headers"
2013 _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
2014 fi
2015 fi
2016 if [ -z "$_CACHED_NONCE" ]; then
2017 _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
2018 nonceurl="$ACME_DIRECTORY"
2019 _headers="$(_get "$nonceurl" "onlyheader")"
2020 _debug2 _headers "$_headers"
2021 _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
2022 fi
2023 if [ -z "$_CACHED_NONCE" ] && [ "$ACME_NEW_NONCE" ]; then
2024 _debug2 "Get nonce with GET. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
2025 nonceurl="$ACME_NEW_NONCE"
2026 _headers="$(_get "$nonceurl" "onlyheader")"
2027 _debug2 _headers "$_headers"
2028 _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
2029 fi
2030 _debug2 _CACHED_NONCE "$_CACHED_NONCE"
2031 if [ "$?" != "0" ]; then
2032 _err "Can not connect to $nonceurl to get nonce."
2033 return 1
2034 fi
2035 else
2036 _debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE"
2037 fi
2038 nonce="$_CACHED_NONCE"
2039 _debug2 nonce "$nonce"
2040 if [ -z "$nonce" ]; then
2041 _info "Could not get nonce, let's try again."
2042 _sleep 2
2043 continue
2044 fi
2045 if [ "$ACME_VERSION" = "2" ]; then
2046 if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then
2047 protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
2048 elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then
2049 protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
2050 else
2051 protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}'
2052 fi
2053 else
2054 protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
2055 fi
2056 _debug3 protected "$protected"
2057
2058 protected64="$(printf "%s" "$protected" | _base64 | _url_replace)"
2059 _debug3 protected64 "$protected64"
2060
2061 if ! _sig_t="$(printf "%s" "$protected64.$payload64" | _sign "$keyfile" "sha256")"; then
2062 _err "Sign request failed."
2063 return 1
2064 fi
2065 _debug3 _sig_t "$_sig_t"
2066
2067 sig="$(printf "%s" "$_sig_t" | _url_replace)"
2068 _debug3 sig "$sig"
2069
2070 body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
2071 _debug3 body "$body"
2072
2073 response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")"
2074 _CACHED_NONCE=""
2075
2076 if [ "$?" != "0" ]; then
2077 _err "Can not post to $url"
2078 return 1
2079 fi
2080
2081 responseHeaders="$(cat "$HTTP_HEADER")"
2082 _debug2 responseHeaders "$responseHeaders"
2083
2084 code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
2085 _debug code "$code"
2086
2087 _debug2 original "$response"
2088 if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then
2089 response="$(echo "$response" | _normalizeJson | _json_decode)"
2090 fi
2091 _debug2 response "$response"
2092
2093 _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
2094
2095 if ! _startswith "$code" "2"; then
2096 _body="$response"
2097 if [ "$needbase64" ]; then
2098 _body="$(echo "$_body" | _dbase64 multiline)"
2099 _debug3 _body "$_body"
2100 fi
2101
2102 if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then
2103 _info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds."
2104 _CACHED_NONCE=""
2105 _sleep $_sleep_retry_sec
2106 continue
2107 fi
2108 fi
2109 return 0
2110 done
2111 _info "Giving up sending to CA server after $MAX_REQUEST_RETRY_TIMES retries."
2112 return 1
2113
2114 }
2115
2116 #setopt "file" "opt" "=" "value" [";"]
2117 _setopt() {
2118 __conf="$1"
2119 __opt="$2"
2120 __sep="$3"
2121 __val="$4"
2122 __end="$5"
2123 if [ -z "$__opt" ]; then
2124 _usage usage: _setopt '"file" "opt" "=" "value" [";"]'
2125 return
2126 fi
2127 if [ ! -f "$__conf" ]; then
2128 touch "$__conf"
2129 fi
2130
2131 if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then
2132 _debug3 OK
2133 if _contains "$__val" "&"; then
2134 __val="$(echo "$__val" | sed 's/&/\\&/g')"
2135 fi
2136 text="$(cat "$__conf")"
2137 printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
2138
2139 elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then
2140 if _contains "$__val" "&"; then
2141 __val="$(echo "$__val" | sed 's/&/\\&/g')"
2142 fi
2143 text="$(cat "$__conf")"
2144 printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf"
2145
2146 else
2147 _debug3 APP
2148 echo "$__opt$__sep$__val$__end" >>"$__conf"
2149 fi
2150 _debug3 "$(grep -n "^$__opt$__sep" "$__conf")"
2151 }
2152
2153 #_save_conf file key value base64encode
2154 #save to conf
2155 _save_conf() {
2156 _s_c_f="$1"
2157 _sdkey="$2"
2158 _sdvalue="$3"
2159 _b64encode="$4"
2160 if [ "$_sdvalue" ] && [ "$_b64encode" ]; then
2161 _sdvalue="${B64CONF_START}$(printf "%s" "${_sdvalue}" | _base64)${B64CONF_END}"
2162 fi
2163 if [ "$_s_c_f" ]; then
2164 _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'"
2165 else
2166 _err "config file is empty, can not save $_sdkey=$_sdvalue"
2167 fi
2168 }
2169
2170 #_clear_conf file key
2171 _clear_conf() {
2172 _c_c_f="$1"
2173 _sdkey="$2"
2174 if [ "$_c_c_f" ]; then
2175 _conf_data="$(cat "$_c_c_f")"
2176 echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f"
2177 else
2178 _err "config file is empty, can not clear"
2179 fi
2180 }
2181
2182 #_read_conf file key
2183 _read_conf() {
2184 _r_c_f="$1"
2185 _sdkey="$2"
2186 if [ -f "$_r_c_f" ]; then
2187 _sdv="$(
2188 eval "$(grep "^$_sdkey *=" "$_r_c_f")"
2189 eval "printf \"%s\" \"\$$_sdkey\""
2190 )"
2191 if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then
2192 _sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)"
2193 fi
2194 printf "%s" "$_sdv"
2195 else
2196 _debug "config file is empty, can not read $_sdkey"
2197 fi
2198 }
2199
2200 #_savedomainconf key value base64encode
2201 #save to domain.conf
2202 _savedomainconf() {
2203 _save_conf "$DOMAIN_CONF" "$@"
2204 }
2205
2206 #_cleardomainconf key
2207 _cleardomainconf() {
2208 _clear_conf "$DOMAIN_CONF" "$1"
2209 }
2210
2211 #_readdomainconf key
2212 _readdomainconf() {
2213 _read_conf "$DOMAIN_CONF" "$1"
2214 }
2215
2216 #key value base64encode
2217 _savedeployconf() {
2218 _savedomainconf "SAVED_$1" "$2" "$3"
2219 #remove later
2220 _cleardomainconf "$1"
2221 }
2222
2223 #key
2224 _getdeployconf() {
2225 _rac_key="$1"
2226 _rac_value="$(eval echo \$"$_rac_key")"
2227 if [ "$_rac_value" ]; then
2228 if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then
2229 _debug2 "trim quotation marks"
2230 eval "export $_rac_key=$_rac_value"
2231 fi
2232 return 0 # do nothing
2233 fi
2234 _saved=$(_readdomainconf "SAVED_$_rac_key")
2235 eval "export $_rac_key=\"$_saved\""
2236 }
2237
2238 #_saveaccountconf key value base64encode
2239 _saveaccountconf() {
2240 _save_conf "$ACCOUNT_CONF_PATH" "$@"
2241 }
2242
2243 #key value base64encode
2244 _saveaccountconf_mutable() {
2245 _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2" "$3"
2246 #remove later
2247 _clearaccountconf "$1"
2248 }
2249
2250 #key
2251 _readaccountconf() {
2252 _read_conf "$ACCOUNT_CONF_PATH" "$1"
2253 }
2254
2255 #key
2256 _readaccountconf_mutable() {
2257 _rac_key="$1"
2258 _readaccountconf "SAVED_$_rac_key"
2259 }
2260
2261 #_clearaccountconf key
2262 _clearaccountconf() {
2263 _clear_conf "$ACCOUNT_CONF_PATH" "$1"
2264 }
2265
2266 #_savecaconf key value
2267 _savecaconf() {
2268 _save_conf "$CA_CONF" "$1" "$2"
2269 }
2270
2271 #_readcaconf key
2272 _readcaconf() {
2273 _read_conf "$CA_CONF" "$1"
2274 }
2275
2276 #_clearaccountconf key
2277 _clearcaconf() {
2278 _clear_conf "$CA_CONF" "$1"
2279 }
2280
2281 # content localaddress
2282 _startserver() {
2283 content="$1"
2284 ncaddr="$2"
2285 _debug "content" "$content"
2286 _debug "ncaddr" "$ncaddr"
2287
2288 _debug "startserver: $$"
2289
2290 _debug Le_HTTPPort "$Le_HTTPPort"
2291 _debug Le_Listen_V4 "$Le_Listen_V4"
2292 _debug Le_Listen_V6 "$Le_Listen_V6"
2293
2294 _NC="socat"
2295 if [ "$Le_Listen_V4" ]; then
2296 _NC="$_NC -4"
2297 elif [ "$Le_Listen_V6" ]; then
2298 _NC="$_NC -6"
2299 fi
2300
2301 if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
2302 _NC="$_NC -d -d -v"
2303 fi
2304
2305 SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork
2306
2307 #Adding bind to local-address
2308 if [ "$ncaddr" ]; then
2309 SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}"
2310 fi
2311
2312 _content_len="$(printf "%s" "$content" | wc -c)"
2313 _debug _content_len "$_content_len"
2314 _debug "_NC" "$_NC $SOCAT_OPTIONS"
2315 $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
2316 echo 'HTTP/1.0 200 OK'; \
2317 echo 'Content-Length\: $_content_len'; \
2318 echo ''; \
2319 printf -- '$content';" &
2320 serverproc="$!"
2321 }
2322
2323 _stopserver() {
2324 pid="$1"
2325 _debug "pid" "$pid"
2326 if [ -z "$pid" ]; then
2327 return
2328 fi
2329
2330 kill $pid
2331
2332 }
2333
2334 # sleep sec
2335 _sleep() {
2336 _sleep_sec="$1"
2337 if [ "$__INTERACTIVE" ]; then
2338 _sleep_c="$_sleep_sec"
2339 while [ "$_sleep_c" -ge "0" ]; do
2340 printf "\r \r"
2341 __green "$_sleep_c"
2342 _sleep_c="$(_math "$_sleep_c" - 1)"
2343 sleep 1
2344 done
2345 printf "\r"
2346 else
2347 sleep "$_sleep_sec"
2348 fi
2349 }
2350
2351 # _starttlsserver san_a san_b port content _ncaddr acmeValidationv1
2352 _starttlsserver() {
2353 _info "Starting tls server."
2354 san_a="$1"
2355 san_b="$2"
2356 port="$3"
2357 content="$4"
2358 opaddr="$5"
2359 acmeValidationv1="$6"
2360
2361 _debug san_a "$san_a"
2362 _debug san_b "$san_b"
2363 _debug port "$port"
2364 _debug acmeValidationv1 "$acmeValidationv1"
2365
2366 #create key TLS_KEY
2367 if ! _createkey "2048" "$TLS_KEY"; then
2368 _err "Create tls validation key error."
2369 return 1
2370 fi
2371
2372 #create csr
2373 alt="$san_a"
2374 if [ "$san_b" ]; then
2375 alt="$alt,$san_b"
2376 fi
2377 if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then
2378 _err "Create tls validation csr error."
2379 return 1
2380 fi
2381
2382 #self signed
2383 if ! _signcsr "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$TLS_CERT"; then
2384 _err "Create tls validation cert error."
2385 return 1
2386 fi
2387
2388 __S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -www -cert $TLS_CERT -key $TLS_KEY "
2389 if [ "$opaddr" ]; then
2390 __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
2391 else
2392 __S_OPENSSL="$__S_OPENSSL -accept $port"
2393 fi
2394
2395 _debug Le_Listen_V4 "$Le_Listen_V4"
2396 _debug Le_Listen_V6 "$Le_Listen_V6"
2397 if [ "$Le_Listen_V4" ]; then
2398 __S_OPENSSL="$__S_OPENSSL -4"
2399 elif [ "$Le_Listen_V6" ]; then
2400 __S_OPENSSL="$__S_OPENSSL -6"
2401 fi
2402
2403 if [ "$acmeValidationv1" ]; then
2404 __S_OPENSSL="$__S_OPENSSL -alpn acme-tls/1"
2405 fi
2406
2407 _debug "$__S_OPENSSL"
2408 if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
2409 $__S_OPENSSL -tlsextdebug &
2410 else
2411 $__S_OPENSSL >/dev/null 2>&1 &
2412 fi
2413
2414 serverproc="$!"
2415 sleep 1
2416 _debug serverproc "$serverproc"
2417 }
2418
2419 #file
2420 _readlink() {
2421 _rf="$1"
2422 if ! readlink -f "$_rf" 2>/dev/null; then
2423 if _startswith "$_rf" "/"; then
2424 echo "$_rf"
2425 return 0
2426 fi
2427 echo "$(pwd)/$_rf" | _conapath
2428 fi
2429 }
2430
2431 _conapath() {
2432 sed "s#/\./#/#g"
2433 }
2434
2435 __initHome() {
2436 if [ -z "$_SCRIPT_HOME" ]; then
2437 if _exists readlink && _exists dirname; then
2438 _debug "Lets find script dir."
2439 _debug "_SCRIPT_" "$_SCRIPT_"
2440 _script="$(_readlink "$_SCRIPT_")"
2441 _debug "_script" "$_script"
2442 _script_home="$(dirname "$_script")"
2443 _debug "_script_home" "$_script_home"
2444 if [ -d "$_script_home" ]; then
2445 _SCRIPT_HOME="$_script_home"
2446 else
2447 _err "It seems the script home is not correct:$_script_home"
2448 fi
2449 fi
2450 fi
2451
2452 # if [ -z "$LE_WORKING_DIR" ]; then
2453 # if [ -f "$DEFAULT_INSTALL_HOME/account.conf" ]; then
2454 # _debug "It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME"
2455 # LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
2456 # else
2457 # LE_WORKING_DIR="$_SCRIPT_HOME"
2458 # fi
2459 # fi
2460
2461 if [ -z "$LE_WORKING_DIR" ]; then
2462 _debug "Using default home:$DEFAULT_INSTALL_HOME"
2463 LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
2464 fi
2465 export LE_WORKING_DIR
2466
2467 if [ -z "$LE_CONFIG_HOME" ]; then
2468 LE_CONFIG_HOME="$LE_WORKING_DIR"
2469 fi
2470 _debug "Using config home:$LE_CONFIG_HOME"
2471 export LE_CONFIG_HOME
2472
2473 _DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf"
2474
2475 if [ -z "$ACCOUNT_CONF_PATH" ]; then
2476 if [ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]; then
2477 . "$_DEFAULT_ACCOUNT_CONF_PATH"
2478 fi
2479 fi
2480
2481 if [ -z "$ACCOUNT_CONF_PATH" ]; then
2482 ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
2483 fi
2484 _debug3 ACCOUNT_CONF_PATH "$ACCOUNT_CONF_PATH"
2485 DEFAULT_LOG_FILE="$LE_CONFIG_HOME/$PROJECT_NAME.log"
2486
2487 DEFAULT_CA_HOME="$LE_CONFIG_HOME/ca"
2488
2489 if [ -z "$LE_TEMP_DIR" ]; then
2490 LE_TEMP_DIR="$LE_CONFIG_HOME/tmp"
2491 fi
2492 }
2493
2494 #server
2495 _initAPI() {
2496 _api_server="${1:-$ACME_DIRECTORY}"
2497 _debug "_init api for server: $_api_server"
2498
2499 if [ -z "$ACME_NEW_ACCOUNT" ]; then
2500 response=$(_get "$_api_server")
2501 if [ "$?" != "0" ]; then
2502 _debug2 "response" "$response"
2503 _err "Can not init api."
2504 return 1
2505 fi
2506 response=$(echo "$response" | _json_decode)
2507 _debug2 "response" "$response"
2508
2509 ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'key-change" *: *"[^"]*"' | cut -d '"' -f 3)
2510 if [ -z "$ACME_KEY_CHANGE" ]; then
2511 ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3)
2512 fi
2513 export ACME_KEY_CHANGE
2514
2515 ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'new-authz" *: *"[^"]*"' | cut -d '"' -f 3)
2516 if [ -z "$ACME_NEW_AUTHZ" ]; then
2517 ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3)
2518 fi
2519 export ACME_NEW_AUTHZ
2520
2521 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3)
2522 ACME_NEW_ORDER_RES="new-cert"
2523 if [ -z "$ACME_NEW_ORDER" ]; then
2524 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
2525 ACME_NEW_ORDER_RES="new-order"
2526 if [ -z "$ACME_NEW_ORDER" ]; then
2527 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3)
2528 fi
2529 fi
2530 export ACME_NEW_ORDER
2531 export ACME_NEW_ORDER_RES
2532
2533 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3)
2534 ACME_NEW_ACCOUNT_RES="new-reg"
2535 if [ -z "$ACME_NEW_ACCOUNT" ]; then
2536 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
2537 ACME_NEW_ACCOUNT_RES="new-account"
2538 if [ -z "$ACME_NEW_ACCOUNT" ]; then
2539 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3)
2540 if [ "$ACME_NEW_ACCOUNT" ]; then
2541 export ACME_VERSION=2
2542 fi
2543 fi
2544 fi
2545 export ACME_NEW_ACCOUNT
2546 export ACME_NEW_ACCOUNT_RES
2547
2548 ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
2549 if [ -z "$ACME_REVOKE_CERT" ]; then
2550 ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3)
2551 fi
2552 export ACME_REVOKE_CERT
2553
2554 ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3)
2555 if [ -z "$ACME_NEW_NONCE" ]; then
2556 ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3)
2557 fi
2558 export ACME_NEW_NONCE
2559
2560 ACME_AGREEMENT=$(echo "$response" | _egrep_o 'terms-of-service" *: *"[^"]*"' | cut -d '"' -f 3)
2561 if [ -z "$ACME_AGREEMENT" ]; then
2562 ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
2563 fi
2564 export ACME_AGREEMENT
2565
2566 _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
2567 _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
2568 _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
2569 _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT"
2570 _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
2571 _debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
2572 _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE"
2573 _debug "ACME_VERSION" "$ACME_VERSION"
2574
2575 fi
2576 }
2577
2578 #[domain] [keylength or isEcc flag]
2579 _initpath() {
2580 domain="$1"
2581 _ilength="$2"
2582
2583 __initHome
2584
2585 if [ -f "$ACCOUNT_CONF_PATH" ]; then
2586 . "$ACCOUNT_CONF_PATH"
2587 fi
2588
2589 if [ "$_ACME_IN_CRON" ]; then
2590 if [ ! "$_USER_PATH_EXPORTED" ]; then
2591 _USER_PATH_EXPORTED=1
2592 export PATH="$USER_PATH:$PATH"
2593 fi
2594 fi
2595
2596 if [ -z "$CA_HOME" ]; then
2597 CA_HOME="$DEFAULT_CA_HOME"
2598 fi
2599
2600 if [ -z "$ACME_DIRECTORY" ]; then
2601 if [ "$STAGE" ]; then
2602 ACME_DIRECTORY="$DEFAULT_STAGING_CA"
2603 _info "Using ACME_DIRECTORY: $ACME_DIRECTORY"
2604 else
2605 default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER")
2606 _debug default_acme_server "$default_acme_server"
2607 if [ "$default_acme_server" ]; then
2608 ACME_DIRECTORY="$default_acme_server"
2609 else
2610 ACME_DIRECTORY="$DEFAULT_CA"
2611 fi
2612 fi
2613 fi
2614
2615 _debug ACME_DIRECTORY "$ACME_DIRECTORY"
2616 _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
2617 _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST"
2618
2619 CA_DIR="$CA_HOME/$_ACME_SERVER_HOST"
2620
2621 _DEFAULT_CA_CONF="$CA_DIR/ca.conf"
2622
2623 if [ -z "$CA_CONF" ]; then
2624 CA_CONF="$_DEFAULT_CA_CONF"
2625 fi
2626 _debug3 CA_CONF "$CA_CONF"
2627
2628 if [ -f "$CA_CONF" ]; then
2629 . "$CA_CONF"
2630 fi
2631
2632 if [ -z "$ACME_DIR" ]; then
2633 ACME_DIR="/home/.acme"
2634 fi
2635
2636 if [ -z "$APACHE_CONF_BACKUP_DIR" ]; then
2637 APACHE_CONF_BACKUP_DIR="$LE_CONFIG_HOME"
2638 fi
2639
2640 if [ -z "$USER_AGENT" ]; then
2641 USER_AGENT="$DEFAULT_USER_AGENT"
2642 fi
2643
2644 if [ -z "$HTTP_HEADER" ]; then
2645 HTTP_HEADER="$LE_CONFIG_HOME/http.header"
2646 fi
2647
2648 _OLD_ACCOUNT_KEY="$LE_WORKING_DIR/account.key"
2649 _OLD_ACCOUNT_JSON="$LE_WORKING_DIR/account.json"
2650
2651 _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key"
2652 _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json"
2653 if [ -z "$ACCOUNT_KEY_PATH" ]; then
2654 ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH"
2655 fi
2656
2657 if [ -z "$ACCOUNT_JSON_PATH" ]; then
2658 ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH"
2659 fi
2660
2661 _DEFAULT_CERT_HOME="$LE_CONFIG_HOME"
2662 if [ -z "$CERT_HOME" ]; then
2663 CERT_HOME="$_DEFAULT_CERT_HOME"
2664 fi
2665
2666 if [ -z "$ACME_OPENSSL_BIN" ] || [ ! -f "$ACME_OPENSSL_BIN" ] || [ ! -x "$ACME_OPENSSL_BIN" ]; then
2667 ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN"
2668 fi
2669
2670 if [ -z "$domain" ]; then
2671 return 0
2672 fi
2673
2674 if [ -z "$DOMAIN_PATH" ]; then
2675 domainhome="$CERT_HOME/$domain"
2676 domainhomeecc="$CERT_HOME/$domain$ECC_SUFFIX"
2677
2678 DOMAIN_PATH="$domainhome"
2679
2680 if _isEccKey "$_ilength"; then
2681 DOMAIN_PATH="$domainhomeecc"
2682 else
2683 if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then
2684 _info "The domain '$domain' seems to have a ECC cert already, please add '$(__red "--ecc")' parameter if you want to use that cert."
2685 fi
2686 fi
2687 _debug DOMAIN_PATH "$DOMAIN_PATH"
2688 fi
2689
2690 if [ -z "$DOMAIN_BACKUP_PATH" ]; then
2691 DOMAIN_BACKUP_PATH="$DOMAIN_PATH/backup"
2692 fi
2693
2694 if [ -z "$DOMAIN_CONF" ]; then
2695 DOMAIN_CONF="$DOMAIN_PATH/$domain.conf"
2696 fi
2697
2698 if [ -z "$DOMAIN_SSL_CONF" ]; then
2699 DOMAIN_SSL_CONF="$DOMAIN_PATH/$domain.csr.conf"
2700 fi
2701
2702 if [ -z "$CSR_PATH" ]; then
2703 CSR_PATH="$DOMAIN_PATH/$domain.csr"
2704 fi
2705 if [ -z "$CERT_KEY_PATH" ]; then
2706 CERT_KEY_PATH="$DOMAIN_PATH/$domain.key"
2707 fi
2708 if [ -z "$CERT_PATH" ]; then
2709 CERT_PATH="$DOMAIN_PATH/$domain.cer"
2710 fi
2711 if [ -z "$CA_CERT_PATH" ]; then
2712 CA_CERT_PATH="$DOMAIN_PATH/ca.cer"
2713 fi
2714 if [ -z "$CERT_FULLCHAIN_PATH" ]; then
2715 CERT_FULLCHAIN_PATH="$DOMAIN_PATH/fullchain.cer"
2716 fi
2717 if [ -z "$CERT_PFX_PATH" ]; then
2718 CERT_PFX_PATH="$DOMAIN_PATH/$domain.pfx"
2719 fi
2720 if [ -z "$CERT_PKCS8_PATH" ]; then
2721 CERT_PKCS8_PATH="$DOMAIN_PATH/$domain.pkcs8"
2722 fi
2723
2724 if [ -z "$TLS_CONF" ]; then
2725 TLS_CONF="$DOMAIN_PATH/tls.validation.conf"
2726 fi
2727 if [ -z "$TLS_CERT" ]; then
2728 TLS_CERT="$DOMAIN_PATH/tls.validation.cert"
2729 fi
2730 if [ -z "$TLS_KEY" ]; then
2731 TLS_KEY="$DOMAIN_PATH/tls.validation.key"
2732 fi
2733 if [ -z "$TLS_CSR" ]; then
2734 TLS_CSR="$DOMAIN_PATH/tls.validation.csr"
2735 fi
2736
2737 }
2738
2739 _exec() {
2740 if [ -z "$_EXEC_TEMP_ERR" ]; then
2741 _EXEC_TEMP_ERR="$(_mktemp)"
2742 fi
2743
2744 if [ "$_EXEC_TEMP_ERR" ]; then
2745 eval "$@ 2>>$_EXEC_TEMP_ERR"
2746 else
2747 eval "$@"
2748 fi
2749 }
2750
2751 _exec_err() {
2752 [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR"
2753 }
2754
2755 _apachePath() {
2756 _APACHECTL="apachectl"
2757 if ! _exists apachectl; then
2758 if _exists apache2ctl; then
2759 _APACHECTL="apache2ctl"
2760 else
2761 _err "'apachectl not found. It seems that apache is not installed, or you are not root user.'"
2762 _err "Please use webroot mode to try again."
2763 return 1
2764 fi
2765 fi
2766
2767 if ! _exec $_APACHECTL -V >/dev/null; then
2768 _exec_err
2769 return 1
2770 fi
2771
2772 if [ "$APACHE_HTTPD_CONF" ]; then
2773 _saveaccountconf APACHE_HTTPD_CONF "$APACHE_HTTPD_CONF"
2774 httpdconf="$APACHE_HTTPD_CONF"
2775 httpdconfname="$(basename "$httpdconfname")"
2776 else
2777 httpdconfname="$($_APACHECTL -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"')"
2778 _debug httpdconfname "$httpdconfname"
2779
2780 if [ -z "$httpdconfname" ]; then
2781 _err "Can not read apache config file."
2782 return 1
2783 fi
2784
2785 if _startswith "$httpdconfname" '/'; then
2786 httpdconf="$httpdconfname"
2787 httpdconfname="$(basename "$httpdconfname")"
2788 else
2789 httpdroot="$($_APACHECTL -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"')"
2790 _debug httpdroot "$httpdroot"
2791 httpdconf="$httpdroot/$httpdconfname"
2792 httpdconfname="$(basename "$httpdconfname")"
2793 fi
2794 fi
2795 _debug httpdconf "$httpdconf"
2796 _debug httpdconfname "$httpdconfname"
2797 if [ ! -f "$httpdconf" ]; then
2798 _err "Apache Config file not found" "$httpdconf"
2799 return 1
2800 fi
2801 return 0
2802 }
2803
2804 _restoreApache() {
2805 if [ -z "$usingApache" ]; then
2806 return 0
2807 fi
2808 _initpath
2809 if ! _apachePath; then
2810 return 1
2811 fi
2812
2813 if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]; then
2814 _debug "No config file to restore."
2815 return 0
2816 fi
2817
2818 cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf"
2819 _debug "Restored: $httpdconf."
2820 if ! _exec $_APACHECTL -t; then
2821 _exec_err
2822 _err "Sorry, restore apache config error, please contact me."
2823 return 1
2824 fi
2825 _debug "Restored successfully."
2826 rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
2827 return 0
2828 }
2829
2830 _setApache() {
2831 _initpath
2832 if ! _apachePath; then
2833 return 1
2834 fi
2835
2836 #test the conf first
2837 _info "Checking if there is an error in the apache config file before starting."
2838
2839 if ! _exec "$_APACHECTL" -t >/dev/null; then
2840 _exec_err
2841 _err "The apache config file has error, please fix it first, then try again."
2842 _err "Don't worry, there is nothing changed to your system."
2843 return 1
2844 else
2845 _info "OK"
2846 fi
2847
2848 #backup the conf
2849 _debug "Backup apache config file" "$httpdconf"
2850 if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then
2851 _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed."
2852 _err "This might be a bug of $PROJECT_NAME , please report issue: $PROJECT"
2853 return 1
2854 fi
2855 _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
2856 _info "In case there is an error that can not be restored automatically, you may try restore it yourself."
2857 _info "The backup file will be deleted on success, just forget it."
2858
2859 #add alias
2860
2861 apacheVer="$($_APACHECTL -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2)"
2862 _debug "apacheVer" "$apacheVer"
2863 apacheMajor="$(echo "$apacheVer" | cut -d . -f 1)"
2864 apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
2865
2866 if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then
2867 echo "
2868 Alias /.well-known/acme-challenge $ACME_DIR
2869
2870 <Directory $ACME_DIR >
2871 Require all granted
2872 </Directory>
2873 " >>"$httpdconf"
2874 else
2875 echo "
2876 Alias /.well-known/acme-challenge $ACME_DIR
2877
2878 <Directory $ACME_DIR >
2879 Order allow,deny
2880 Allow from all
2881 </Directory>
2882 " >>"$httpdconf"
2883 fi
2884
2885 _msg="$($_APACHECTL -t 2>&1)"
2886 if [ "$?" != "0" ]; then
2887 _err "Sorry, apache config error"
2888 if _restoreApache; then
2889 _err "The apache config file is restored."
2890 else
2891 _err "Sorry, The apache config file can not be restored, please report bug."
2892 fi
2893 return 1
2894 fi
2895
2896 if [ ! -d "$ACME_DIR" ]; then
2897 mkdir -p "$ACME_DIR"
2898 chmod 755 "$ACME_DIR"
2899 fi
2900
2901 if ! _exec "$_APACHECTL" graceful; then
2902 _exec_err
2903 _err "$_APACHECTL graceful error, please contact me."
2904 _restoreApache
2905 return 1
2906 fi
2907 usingApache="1"
2908 return 0
2909 }
2910
2911 #find the real nginx conf file
2912 #backup
2913 #set the nginx conf
2914 #returns the real nginx conf file
2915 _setNginx() {
2916 _d="$1"
2917 _croot="$2"
2918 _thumbpt="$3"
2919
2920 FOUND_REAL_NGINX_CONF=""
2921 FOUND_REAL_NGINX_CONF_LN=""
2922 BACKUP_NGINX_CONF=""
2923 _debug _croot "$_croot"
2924 _start_f="$(echo "$_croot" | cut -d : -f 2)"
2925 _debug _start_f "$_start_f"
2926 if [ -z "$_start_f" ]; then
2927 _debug "find start conf from nginx command"
2928 if [ -z "$NGINX_CONF" ]; then
2929 if ! _exists "nginx"; then
2930 _err "nginx command is not found."
2931 return 1
2932 fi
2933 NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")"
2934 _debug NGINX_CONF "$NGINX_CONF"
2935 NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)"
2936 _debug NGINX_CONF "$NGINX_CONF"
2937 if [ -z "$NGINX_CONF" ]; then
2938 _err "Can not find nginx conf."
2939 NGINX_CONF=""
2940 return 1
2941 fi
2942 if [ ! -f "$NGINX_CONF" ]; then
2943 _err "'$NGINX_CONF' doesn't exist."
2944 NGINX_CONF=""
2945 return 1
2946 fi
2947 _debug "Found nginx conf file:$NGINX_CONF"
2948 fi
2949 _start_f="$NGINX_CONF"
2950 fi
2951 _debug "Start detect nginx conf for $_d from:$_start_f"
2952 if ! _checkConf "$_d" "$_start_f"; then
2953 _err "Can not find conf file for domain $d"
2954 return 1
2955 fi
2956 _info "Found conf file: $FOUND_REAL_NGINX_CONF"
2957
2958 _ln=$FOUND_REAL_NGINX_CONF_LN
2959 _debug "_ln" "$_ln"
2960
2961 _lnn=$(_math $_ln + 1)
2962 _debug _lnn "$_lnn"
2963 _start_tag="$(sed -n "$_lnn,${_lnn}p" "$FOUND_REAL_NGINX_CONF")"
2964 _debug "_start_tag" "$_start_tag"
2965 if [ "$_start_tag" = "$NGINX_START" ]; then
2966 _info "The domain $_d is already configured, skip"
2967 FOUND_REAL_NGINX_CONF=""
2968 return 0
2969 fi
2970
2971 mkdir -p "$DOMAIN_BACKUP_PATH"
2972 _backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf"
2973 _debug _backup_conf "$_backup_conf"
2974 BACKUP_NGINX_CONF="$_backup_conf"
2975 _info "Backup $FOUND_REAL_NGINX_CONF to $_backup_conf"
2976 if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then
2977 _err "backup error."
2978 FOUND_REAL_NGINX_CONF=""
2979 return 1
2980 fi
2981
2982 if ! _exists "nginx"; then
2983 _err "nginx command is not found."
2984 return 1
2985 fi
2986 _info "Check the nginx conf before setting up."
2987 if ! _exec "nginx -t" >/dev/null; then
2988 _exec_err
2989 return 1
2990 fi
2991
2992 _info "OK, Set up nginx config file"
2993
2994 if ! sed -n "1,${_ln}p" "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"; then
2995 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
2996 _err "write nginx conf error, but don't worry, the file is restored to the original version."
2997 return 1
2998 fi
2999
3000 echo "$NGINX_START
3001 location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
3002 default_type text/plain;
3003 return 200 \"\$1.$_thumbpt\";
3004 }
3005 #NGINX_START
3006 " >>"$FOUND_REAL_NGINX_CONF"
3007
3008 if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then
3009 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3010 _err "write nginx conf error, but don't worry, the file is restored."
3011 return 1
3012 fi
3013 _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)"
3014 _info "nginx conf is done, let's check it again."
3015 if ! _exec "nginx -t" >/dev/null; then
3016 _exec_err
3017 _err "It seems that nginx conf was broken, let's restore."
3018 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3019 return 1
3020 fi
3021
3022 _info "Reload nginx"
3023 if ! _exec "nginx -s reload" >/dev/null; then
3024 _exec_err
3025 _err "It seems that nginx reload error, let's restore."
3026 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3027 return 1
3028 fi
3029
3030 return 0
3031 }
3032
3033 #d , conf
3034 _checkConf() {
3035 _d="$1"
3036 _c_file="$2"
3037 _debug "Start _checkConf from:$_c_file"
3038 if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
3039 _debug "wildcard"
3040 for _w_f in $2; do
3041 if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then
3042 return 0
3043 fi
3044 done
3045 #not found
3046 return 1
3047 elif [ -f "$2" ]; then
3048 _debug "single"
3049 if _isRealNginxConf "$1" "$2"; then
3050 _debug "$2 is found."
3051 FOUND_REAL_NGINX_CONF="$2"
3052 return 0
3053 fi
3054 if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then
3055 _debug "Try include files"
3056 for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do
3057 _debug "check included $included"
3058 if _checkConf "$1" "$included"; then
3059 return 0
3060 fi
3061 done
3062 fi
3063 return 1
3064 else
3065 _debug "$2 not found."
3066 return 1
3067 fi
3068 return 1
3069 }
3070
3071 #d , conf
3072 _isRealNginxConf() {
3073 _debug "_isRealNginxConf $1 $2"
3074 if [ -f "$2" ]; then
3075 for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do
3076 _debug _fln "$_fln"
3077 if [ "$_fln" ]; then
3078 _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *" | grep -v server_name | _tail_n 1)
3079 _debug "_start" "$_start"
3080 _start_n=$(echo "$_start" | cut -d : -f 1)
3081 _start_nn=$(_math $_start_n + 1)
3082 _debug "_start_n" "$_start_n"
3083 _debug "_start_nn" "$_start_nn"
3084
3085 _left="$(sed -n "${_start_nn},99999p" "$2")"
3086 _debug2 _left "$_left"
3087 _end="$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | grep -v server_name | _head_n 1)"
3088 _debug "_end" "$_end"
3089 if [ "$_end" ]; then
3090 _end_n=$(echo "$_end" | cut -d : -f 1)
3091 _debug "_end_n" "$_end_n"
3092 _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
3093 else
3094 _seg_n="$_left"
3095 fi
3096
3097 _debug "_seg_n" "$_seg_n"
3098
3099 _skip_ssl=1
3100 for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do
3101 if [ "$_listen_i" ]; then
3102 if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl")" ]; then
3103 _debug2 "$_listen_i is ssl"
3104 else
3105 _debug2 "$_listen_i is plain text"
3106 _skip_ssl=""
3107 break
3108 fi
3109 fi
3110 done
3111
3112 if [ "$_skip_ssl" = "1" ]; then
3113 _debug "ssl on, skip"
3114 else
3115 FOUND_REAL_NGINX_CONF_LN=$_fln
3116 _debug3 "found FOUND_REAL_NGINX_CONF_LN" "$FOUND_REAL_NGINX_CONF_LN"
3117 return 0
3118 fi
3119 fi
3120 done
3121 fi
3122 return 1
3123 }
3124
3125 #restore all the nginx conf
3126 _restoreNginx() {
3127 if [ -z "$NGINX_RESTORE_VLIST" ]; then
3128 _debug "No need to restore nginx, skip."
3129 return
3130 fi
3131 _debug "_restoreNginx"
3132 _debug "NGINX_RESTORE_VLIST" "$NGINX_RESTORE_VLIST"
3133
3134 for ng_entry in $(echo "$NGINX_RESTORE_VLIST" | tr "$dvsep" ' '); do
3135 _debug "ng_entry" "$ng_entry"
3136 _nd=$(echo "$ng_entry" | cut -d "$sep" -f 1)
3137 _ngconf=$(echo "$ng_entry" | cut -d "$sep" -f 2)
3138 _ngbackupconf=$(echo "$ng_entry" | cut -d "$sep" -f 3)
3139 _info "Restoring from $_ngbackupconf to $_ngconf"
3140 cat "$_ngbackupconf" >"$_ngconf"
3141 done
3142
3143 _info "Reload nginx"
3144 if ! _exec "nginx -s reload" >/dev/null; then
3145 _exec_err
3146 _err "It seems that nginx reload error, please report bug."
3147 return 1
3148 fi
3149 return 0
3150 }
3151
3152 _clearup() {
3153 _stopserver "$serverproc"
3154 serverproc=""
3155 _restoreApache
3156 _restoreNginx
3157 _clearupdns
3158 if [ -z "$DEBUG" ]; then
3159 rm -f "$TLS_CONF"
3160 rm -f "$TLS_CERT"
3161 rm -f "$TLS_KEY"
3162 rm -f "$TLS_CSR"
3163 fi
3164 }
3165
3166 _clearupdns() {
3167 _debug "_clearupdns"
3168 _debug "dns_entries" "$dns_entries"
3169
3170 if [ -z "$dns_entries" ]; then
3171 _debug "skip dns."
3172 return
3173 fi
3174 _info "Removing DNS records."
3175
3176 for entry in $dns_entries; do
3177 d=$(_getfield "$entry" 1)
3178 txtdomain=$(_getfield "$entry" 2)
3179 aliasDomain=$(_getfield "$entry" 3)
3180 _currentRoot=$(_getfield "$entry" 4)
3181 txt=$(_getfield "$entry" 5)
3182 d_api=$(_getfield "$entry" 6)
3183 _debug "d" "$d"
3184 _debug "txtdomain" "$txtdomain"
3185 _debug "aliasDomain" "$aliasDomain"
3186 _debug "_currentRoot" "$_currentRoot"
3187 _debug "txt" "$txt"
3188 _debug "d_api" "$d_api"
3189 if [ "$d_api" = "$txt" ]; then
3190 d_api=""
3191 fi
3192
3193 if [ -z "$d_api" ]; then
3194 _info "Not Found domain api file: $d_api"
3195 continue
3196 fi
3197
3198 if [ "$aliasDomain" ]; then
3199 txtdomain="$aliasDomain"
3200 fi
3201
3202 (
3203 if ! . "$d_api"; then
3204 _err "Load file $d_api error. Please check your api file and try again."
3205 return 1
3206 fi
3207
3208 rmcommand="${_currentRoot}_rm"
3209 if ! _exists "$rmcommand"; then
3210 _err "It seems that your api file doesn't define $rmcommand"
3211 return 1
3212 fi
3213 _info "Removing txt: $txt for domain: $txtdomain"
3214 if ! $rmcommand "$txtdomain" "$txt"; then
3215 _err "Error removing txt for domain:$txtdomain"
3216 return 1
3217 fi
3218 _info "Removed: Success"
3219 )
3220
3221 done
3222 }
3223
3224 # webroot removelevel tokenfile
3225 _clearupwebbroot() {
3226 __webroot="$1"
3227 if [ -z "$__webroot" ]; then
3228 _debug "no webroot specified, skip"
3229 return 0
3230 fi
3231
3232 _rmpath=""
3233 if [ "$2" = '1' ]; then
3234 _rmpath="$__webroot/.well-known"
3235 elif [ "$2" = '2' ]; then
3236 _rmpath="$__webroot/.well-known/acme-challenge"
3237 elif [ "$2" = '3' ]; then
3238 _rmpath="$__webroot/.well-known/acme-challenge/$3"
3239 else
3240 _debug "Skip for removelevel:$2"
3241 fi
3242
3243 if [ "$_rmpath" ]; then
3244 if [ "$DEBUG" ]; then
3245 _debug "Debugging, skip removing: $_rmpath"
3246 else
3247 rm -rf "$_rmpath"
3248 fi
3249 fi
3250
3251 return 0
3252
3253 }
3254
3255 _on_before_issue() {
3256 _chk_web_roots="$1"
3257 _chk_main_domain="$2"
3258 _chk_alt_domains="$3"
3259 _chk_pre_hook="$4"
3260 _chk_local_addr="$5"
3261 _debug _on_before_issue
3262 _debug _chk_main_domain "$_chk_main_domain"
3263 _debug _chk_alt_domains "$_chk_alt_domains"
3264 #run pre hook
3265 if [ "$_chk_pre_hook" ]; then
3266 _info "Run pre hook:'$_chk_pre_hook'"
3267 if ! (
3268 cd "$DOMAIN_PATH" && eval "$_chk_pre_hook"
3269 ); then
3270 _err "Error when run pre hook."
3271 return 1
3272 fi
3273 fi
3274
3275 if _hasfield "$_chk_web_roots" "$NO_VALUE"; then
3276 if ! _exists "socat"; then
3277 _err "Please install socat tools first."
3278 return 1
3279 fi
3280 fi
3281
3282 _debug Le_LocalAddress "$_chk_local_addr"
3283
3284 _index=1
3285 _currentRoot=""
3286 _addrIndex=1
3287 _w_index=1
3288 while true; do
3289 d="$(echo "$_chk_main_domain,$_chk_alt_domains," | cut -d , -f "$_w_index")"
3290 _w_index="$(_math "$_w_index" + 1)"
3291 _debug d "$d"
3292 if [ -z "$d" ]; then
3293 break
3294 fi
3295 _debug "Check for domain" "$d"
3296 _currentRoot="$(_getfield "$_chk_web_roots" $_index)"
3297 _debug "_currentRoot" "$_currentRoot"
3298 _index=$(_math $_index + 1)
3299 _checkport=""
3300 if [ "$_currentRoot" = "$NO_VALUE" ]; then
3301 _info "Standalone mode."
3302 if [ -z "$Le_HTTPPort" ]; then
3303 Le_HTTPPort=80
3304 _cleardomainconf "Le_HTTPPort"
3305 else
3306 _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
3307 fi
3308 _checkport="$Le_HTTPPort"
3309 elif [ "$_currentRoot" = "$W_ALPN" ]; then
3310 _info "Standalone alpn mode."
3311 if [ -z "$Le_TLSPort" ]; then
3312 Le_TLSPort=443
3313 else
3314 _savedomainconf "Le_TLSPort" "$Le_TLSPort"
3315 fi
3316 _checkport="$Le_TLSPort"
3317 fi
3318
3319 if [ "$_checkport" ]; then
3320 _debug _checkport "$_checkport"
3321 _checkaddr="$(_getfield "$_chk_local_addr" $_addrIndex)"
3322 _debug _checkaddr "$_checkaddr"
3323
3324 _addrIndex="$(_math $_addrIndex + 1)"
3325
3326 _netprc="$(_ss "$_checkport" | grep "$_checkport")"
3327 netprc="$(echo "$_netprc" | grep "$_checkaddr")"
3328 if [ -z "$netprc" ]; then
3329 netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS")"
3330 fi
3331 if [ "$netprc" ]; then
3332 _err "$netprc"
3333 _err "tcp port $_checkport is already used by $(echo "$netprc" | cut -d : -f 4)"
3334 _err "Please stop it first"
3335 return 1
3336 fi
3337 fi
3338 done
3339
3340 if _hasfield "$_chk_web_roots" "apache"; then
3341 if ! _setApache; then
3342 _err "set up apache error. Report error to me."
3343 return 1
3344 fi
3345 else
3346 usingApache=""
3347 fi
3348
3349 }
3350
3351 _on_issue_err() {
3352 _chk_post_hook="$1"
3353 _chk_vlist="$2"
3354 _debug _on_issue_err
3355
3356 if [ "$LOG_FILE" ]; then
3357 _err "Please check log file for more details: $LOG_FILE"
3358 else
3359 _err "Please add '--debug' or '--log' to check more details."
3360 _err "See: $_DEBUG_WIKI"
3361 fi
3362
3363 #run the post hook
3364 if [ "$_chk_post_hook" ]; then
3365 _info "Run post hook:'$_chk_post_hook'"
3366 if ! (
3367 cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
3368 ); then
3369 _err "Error when run post hook."
3370 return 1
3371 fi
3372 fi
3373
3374 #trigger the validation to flush the pending authz
3375 _debug2 "_chk_vlist" "$_chk_vlist"
3376 if [ "$_chk_vlist" ]; then
3377 (
3378 _debug2 "start to deactivate authz"
3379 ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
3380 for ventry in $ventries; do
3381 d=$(echo "$ventry" | cut -d "$sep" -f 1)
3382 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
3383 uri=$(echo "$ventry" | cut -d "$sep" -f 3)
3384 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
3385 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
3386 __trigger_validation "$uri" "$keyauthorization"
3387 done
3388 )
3389 fi
3390
3391 if [ "$_ACME_IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then
3392 _err "$_DNS_MANUAL_ERR"
3393 fi
3394
3395 if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then
3396 _debug "$(_dlg_versions)"
3397 fi
3398
3399 }
3400
3401 _on_issue_success() {
3402 _chk_post_hook="$1"
3403 _chk_renew_hook="$2"
3404 _debug _on_issue_success
3405
3406 #run the post hook
3407 if [ "$_chk_post_hook" ]; then
3408 _info "Run post hook:'$_chk_post_hook'"
3409 if ! (
3410 export CERT_PATH
3411 export CERT_KEY_PATH
3412 export CA_CERT_PATH
3413 export CERT_FULLCHAIN_PATH
3414 export Le_Domain="$_main_domain"
3415 cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
3416 ); then
3417 _err "Error when run post hook."
3418 return 1
3419 fi
3420 fi
3421
3422 #run renew hook
3423 if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then
3424 _info "Run renew hook:'$_chk_renew_hook'"
3425 if ! (
3426 export CERT_PATH
3427 export CERT_KEY_PATH
3428 export CA_CERT_PATH
3429 export CERT_FULLCHAIN_PATH
3430 export Le_Domain="$_main_domain"
3431 cd "$DOMAIN_PATH" && eval "$_chk_renew_hook"
3432 ); then
3433 _err "Error when run renew hook."
3434 return 1
3435 fi
3436 fi
3437
3438 if _hasfield "$Le_Webroot" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
3439 _err "$_DNS_MANUAL_WARN"
3440 fi
3441
3442 }
3443
3444 #account_key_length eab-kid eab-hmac-key
3445 registeraccount() {
3446 _account_key_length="$1"
3447 _eab_id="$2"
3448 _eab_hmac_key="$3"
3449 _initpath
3450 _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
3451 }
3452
3453 __calcAccountKeyHash() {
3454 [ -f "$ACCOUNT_KEY_PATH" ] && _digest sha256 <"$ACCOUNT_KEY_PATH"
3455 }
3456
3457 __calc_account_thumbprint() {
3458 printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
3459 }
3460
3461 _getAccountEmail() {
3462 if [ "$ACCOUNT_EMAIL" ]; then
3463 echo "$ACCOUNT_EMAIL"
3464 return 0
3465 fi
3466 if [ -z "$CA_EMAIL" ]; then
3467 CA_EMAIL="$(_readcaconf CA_EMAIL)"
3468 fi
3469 if [ "$CA_EMAIL" ]; then
3470 echo "$CA_EMAIL"
3471 return 0
3472 fi
3473 _readaccountconf "ACCOUNT_EMAIL"
3474 }
3475
3476 #keylength
3477 _regAccount() {
3478 _initpath
3479 _reg_length="$1"
3480 _eab_id="$2"
3481 _eab_hmac_key="$3"
3482 _debug3 _regAccount "$_regAccount"
3483 _initAPI
3484
3485 mkdir -p "$CA_DIR"
3486 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3487 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3488 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3489 fi
3490
3491 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3492 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3493 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3494 fi
3495
3496 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3497 if ! _create_account_key "$_reg_length"; then
3498 _err "Create account key error."
3499 return 1
3500 fi
3501 fi
3502
3503 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3504 return 1
3505 fi
3506 if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
3507 _savecaconf CA_EAB_KEY_ID "$_eab_id"
3508 _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
3509 fi
3510 _eab_id=$(_readcaconf "CA_EAB_KEY_ID")
3511 _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
3512 _secure_debug3 _eab_id "$_eab_id"
3513 _secure_debug3 _eab_hmac_key "$_eab_hmac_key"
3514 _email="$(_getAccountEmail)"
3515 if [ "$_email" ]; then
3516 _savecaconf "CA_EMAIL" "$_email"
3517 fi
3518 if [ "$ACME_VERSION" = "2" ]; then
3519 if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
3520 if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
3521 _info "No EAB credentials found for ZeroSSL, let's get one"
3522 if [ -z "$_email" ]; then
3523 _err "Please provide a email address for ZeroSSL account."
3524 _err "See ZeroSSL usage: $_ZEROSSL_WIKI"
3525 return 1
3526 fi
3527 _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
3528 if [ "$?" != "0" ]; then
3529 _debug2 "$_eabresp"
3530 _err "Can not get EAB credentials from ZeroSSL."
3531 return 1
3532 fi
3533 _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
3534 if [ -z "$_eab_id" ]; then
3535 _err "Can not resolve _eab_id"
3536 return 1
3537 fi
3538 _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
3539 if [ -z "$_eab_hmac_key" ]; then
3540 _err "Can not resolve _eab_hmac_key"
3541 return 1
3542 fi
3543 _savecaconf CA_EAB_KEY_ID "$_eab_id"
3544 _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
3545 fi
3546 fi
3547 if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
3548 eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
3549 _debug3 eab_protected "$eab_protected"
3550
3551 eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
3552 _debug3 eab_protected64 "$eab_protected64"
3553
3554 eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace)
3555 _debug3 eab_payload64 "$eab_payload64"
3556
3557 eab_sign_t="$eab_protected64.$eab_payload64"
3558 _debug3 eab_sign_t "$eab_sign_t"
3559
3560 key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
3561 _debug3 key_hex "$key_hex"
3562
3563 eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
3564 _debug3 eab_signature "$eab_signature"
3565
3566 externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}"
3567 _debug3 externalBinding "$externalBinding"
3568 fi
3569 if [ "$_email" ]; then
3570 email_sg="\"contact\": [\"mailto:$_email\"], "
3571 fi
3572 regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
3573 else
3574 _reg_res="$ACME_NEW_ACCOUNT_RES"
3575 regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
3576 if [ "$_email" ]; then
3577 regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
3578 fi
3579 fi
3580
3581 _info "Registering account: $ACME_DIRECTORY"
3582
3583 if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then
3584 _err "Register account Error: $response"
3585 return 1
3586 fi
3587
3588 _eabAlreadyBound=""
3589 if [ "$code" = "" ] || [ "$code" = '201' ]; then
3590 echo "$response" >"$ACCOUNT_JSON_PATH"
3591 _info "Registered"
3592 elif [ "$code" = '409' ] || [ "$code" = '200' ]; then
3593 _info "Already registered"
3594 elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then
3595 _info "Already register EAB."
3596 _eabAlreadyBound=1
3597 else
3598 _err "Register account Error: $response"
3599 return 1
3600 fi
3601
3602 if [ -z "$_eabAlreadyBound" ]; then
3603 _debug2 responseHeaders "$responseHeaders"
3604 _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")"
3605 _debug "_accUri" "$_accUri"
3606 if [ -z "$_accUri" ]; then
3607 _err "Can not find account id url."
3608 _err "$responseHeaders"
3609 return 1
3610 fi
3611 _savecaconf "ACCOUNT_URL" "$_accUri"
3612 else
3613 ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
3614 fi
3615 export ACCOUNT_URL="$_accUri"
3616
3617 CA_KEY_HASH="$(__calcAccountKeyHash)"
3618 _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
3619 _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
3620
3621 if [ "$code" = '403' ]; then
3622 _err "It seems that the account key is already deactivated, please use a new account key."
3623 return 1
3624 fi
3625
3626 ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
3627 _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
3628 }
3629
3630 #implement updateaccount
3631 updateaccount() {
3632 _initpath
3633
3634 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3635 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3636 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3637 fi
3638
3639 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3640 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3641 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3642 fi
3643
3644 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3645 _err "Account key is not found at: $ACCOUNT_KEY_PATH"
3646 return 1
3647 fi
3648
3649 _accUri=$(_readcaconf "ACCOUNT_URL")
3650 _debug _accUri "$_accUri"
3651
3652 if [ -z "$_accUri" ]; then
3653 _err "The account url is empty, please run '--update-account' first to update the account info first,"
3654 _err "Then try again."
3655 return 1
3656 fi
3657
3658 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3659 return 1
3660 fi
3661 _initAPI
3662
3663 _email="$(_getAccountEmail)"
3664 if [ "$ACME_VERSION" = "2" ]; then
3665 if [ "$ACCOUNT_EMAIL" ]; then
3666 updjson='{"contact": ["mailto:'$_email'"]}'
3667 else
3668 updjson='{"contact": []}'
3669 fi
3670 else
3671 # ACMEv1: Updates happen the same way a registration is done.
3672 # https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-6.3
3673 _regAccount
3674 return
3675 fi
3676
3677 # this part handles ACMEv2 account updates.
3678 _send_signed_request "$_accUri" "$updjson"
3679
3680 if [ "$code" = '200' ]; then
3681 echo "$response" >"$ACCOUNT_JSON_PATH"
3682 _info "account update success for $_accUri."
3683 else
3684 _info "Error. The account was not updated."
3685 return 1
3686 fi
3687 }
3688
3689 #Implement deactivate account
3690 deactivateaccount() {
3691 _initpath
3692
3693 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3694 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3695 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3696 fi
3697
3698 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3699 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3700 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3701 fi
3702
3703 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3704 _err "Account key is not found at: $ACCOUNT_KEY_PATH"
3705 return 1
3706 fi
3707
3708 _accUri=$(_readcaconf "ACCOUNT_URL")
3709 _debug _accUri "$_accUri"
3710
3711 if [ -z "$_accUri" ]; then
3712 _err "The account url is empty, please run '--update-account' first to update the account info first,"
3713 _err "Then try again."
3714 return 1
3715 fi
3716
3717 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3718 return 1
3719 fi
3720 _initAPI
3721
3722 if [ "$ACME_VERSION" = "2" ]; then
3723 _djson="{\"status\":\"deactivated\"}"
3724 else
3725 _djson="{\"resource\": \"reg\", \"status\":\"deactivated\"}"
3726 fi
3727 if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
3728 _info "Deactivate account success for $_accUri."
3729 _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
3730 elif [ "$code" = "403" ]; then
3731 _info "The account is already deactivated."
3732 _accid=$(_getfield "$_accUri" "999" "/")
3733 else
3734 _err "Deactivate: account failed for $_accUri."
3735 return 1
3736 fi
3737
3738 _debug "Account id: $_accid"
3739 if [ "$_accid" ]; then
3740 _deactivated_account_path="$CA_DIR/deactivated/$_accid"
3741 _debug _deactivated_account_path "$_deactivated_account_path"
3742 if mkdir -p "$_deactivated_account_path"; then
3743 _info "Moving deactivated account info to $_deactivated_account_path/"
3744 mv "$CA_CONF" "$_deactivated_account_path/"
3745 mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/"
3746 mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/"
3747 else
3748 _err "Can not create dir: $_deactivated_account_path, try to remove the deactivated account key."
3749 rm -f "$CA_CONF"
3750 rm -f "$ACCOUNT_JSON_PATH"
3751 rm -f "$ACCOUNT_KEY_PATH"
3752 fi
3753 fi
3754 }
3755
3756 # domain folder file
3757 _findHook() {
3758 _hookdomain="$1"
3759 _hookcat="$2"
3760 _hookname="$3"
3761
3762 if [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname" ]; then
3763 d_api="$_SCRIPT_HOME/$_hookcat/$_hookname"
3764 elif [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname.sh" ]; then
3765 d_api="$_SCRIPT_HOME/$_hookcat/$_hookname.sh"
3766 elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname" ]; then
3767 d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname"
3768 elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname.sh" ]; then
3769 d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname.sh"
3770 elif [ -f "$LE_WORKING_DIR/$_hookname" ]; then
3771 d_api="$LE_WORKING_DIR/$_hookname"
3772 elif [ -f "$LE_WORKING_DIR/$_hookname.sh" ]; then
3773 d_api="$LE_WORKING_DIR/$_hookname.sh"
3774 elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname" ]; then
3775 d_api="$LE_WORKING_DIR/$_hookcat/$_hookname"
3776 elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname.sh" ]; then
3777 d_api="$LE_WORKING_DIR/$_hookcat/$_hookname.sh"
3778 fi
3779
3780 printf "%s" "$d_api"
3781 }
3782
3783 #domain
3784 __get_domain_new_authz() {
3785 _gdnd="$1"
3786 _info "Getting new-authz for domain" "$_gdnd"
3787 _initAPI
3788 _Max_new_authz_retry_times=5
3789 _authz_i=0
3790 while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do
3791 _debug "Try new-authz for the $_authz_i time."
3792 if ! _send_signed_request "${ACME_NEW_AUTHZ}" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$(_idn "$_gdnd")\"}}"; then
3793 _err "Can not get domain new authz."
3794 return 1
3795 fi
3796 if _contains "$response" "No registration exists matching provided key"; then
3797 _err "It seems there is an error, but it's recovered now, please try again."
3798 _err "If you see this message for a second time, please report bug: $(__green "$PROJECT")"
3799 _clearcaconf "CA_KEY_HASH"
3800 break
3801 fi
3802 if ! _contains "$response" "An error occurred while processing your request"; then
3803 _info "The new-authz request is ok."
3804 break
3805 fi
3806 _authz_i="$(_math "$_authz_i" + 1)"
3807 _info "The server is busy, Sleep $_authz_i to retry."
3808 _sleep "$_authz_i"
3809 done
3810
3811 if [ "$_authz_i" = "$_Max_new_authz_retry_times" ]; then
3812 _err "new-authz retry reach the max $_Max_new_authz_retry_times times."
3813 fi
3814
3815 if [ "$code" ] && [ "$code" != '201' ]; then
3816 _err "new-authz error: $response"
3817 return 1
3818 fi
3819
3820 }
3821
3822 #uri keyAuthorization
3823 __trigger_validation() {
3824 _debug2 "Trigger domain validation."
3825 _t_url="$1"
3826 _debug2 _t_url "$_t_url"
3827 _t_key_authz="$2"
3828 _debug2 _t_key_authz "$_t_key_authz"
3829 _t_vtype="$3"
3830 _debug2 _t_vtype "$_t_vtype"
3831 if [ "$ACME_VERSION" = "2" ]; then
3832 _send_signed_request "$_t_url" "{}"
3833 else
3834 _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
3835 fi
3836 }
3837
3838 #endpoint domain type
3839 _ns_lookup_impl() {
3840 _ns_ep="$1"
3841 _ns_domain="$2"
3842 _ns_type="$3"
3843 _debug2 "_ns_ep" "$_ns_ep"
3844 _debug2 "_ns_domain" "$_ns_domain"
3845 _debug2 "_ns_type" "$_ns_type"
3846
3847 response="$(_H1="accept: application/dns-json" _get "$_ns_ep?name=$_ns_domain&type=$_ns_type")"
3848 _ret=$?
3849 _debug2 "response" "$response"
3850 if [ "$_ret" != "0" ]; then
3851 return $_ret
3852 fi
3853 _answers="$(echo "$response" | tr '{}' '<>' | _egrep_o '"Answer":\[[^]]*]' | tr '<>' '\n\n')"
3854 _debug2 "_answers" "$_answers"
3855 echo "$_answers"
3856 }
3857
3858 #domain, type
3859 _ns_lookup_cf() {
3860 _cf_ld="$1"
3861 _cf_ld_type="$2"
3862 _cf_ep="https://cloudflare-dns.com/dns-query"
3863 _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
3864 }
3865
3866 #domain, type
3867 _ns_purge_cf() {
3868 _cf_d="$1"
3869 _cf_d_type="$2"
3870 _debug "Cloudflare purge $_cf_d_type record for domain $_cf_d"
3871 _cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type"
3872 response="$(_post "" "$_cf_purl")"
3873 _debug2 response "$response"
3874 }
3875
3876 #checks if cf server is available
3877 _ns_is_available_cf() {
3878 if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then
3879 return 0
3880 else
3881 return 1
3882 fi
3883 }
3884
3885 #domain, type
3886 _ns_lookup_google() {
3887 _cf_ld="$1"
3888 _cf_ld_type="$2"
3889 _cf_ep="https://dns.google/resolve"
3890 _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
3891 }
3892
3893 #domain, type
3894 _ns_lookup() {
3895 if [ -z "$DOH_USE" ]; then
3896 _debug "Detect dns server first."
3897 if _ns_is_available_cf; then
3898 _debug "Use cloudflare doh server"
3899 export DOH_USE=$DOH_CLOUDFLARE
3900 else
3901 _debug "Use google doh server"
3902 export DOH_USE=$DOH_GOOGLE
3903 fi
3904 fi
3905
3906 if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
3907 _ns_lookup_cf "$@"
3908 else
3909 _ns_lookup_google "$@"
3910 fi
3911
3912 }
3913
3914 #txtdomain, alias, txt
3915 __check_txt() {
3916 _c_txtdomain="$1"
3917 _c_aliasdomain="$2"
3918 _c_txt="$3"
3919 _debug "_c_txtdomain" "$_c_txtdomain"
3920 _debug "_c_aliasdomain" "$_c_aliasdomain"
3921 _debug "_c_txt" "$_c_txt"
3922 _answers="$(_ns_lookup "$_c_aliasdomain" TXT)"
3923 _contains "$_answers" "$_c_txt"
3924
3925 }
3926
3927 #txtdomain
3928 __purge_txt() {
3929 _p_txtdomain="$1"
3930 _debug _p_txtdomain "$_p_txtdomain"
3931 if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
3932 _ns_purge_cf "$_p_txtdomain" "TXT"
3933 else
3934 _debug "no purge api for google dns api, just sleep 5 secs"
3935 _sleep 5
3936 fi
3937
3938 }
3939
3940 #wait and check each dns entries
3941 _check_dns_entries() {
3942 _success_txt=","
3943 _end_time="$(_time)"
3944 _end_time="$(_math "$_end_time" + 1200)" #let's check no more than 20 minutes.
3945
3946 while [ "$(_time)" -le "$_end_time" ]; do
3947 _left=""
3948 for entry in $dns_entries; do
3949 d=$(_getfield "$entry" 1)
3950 txtdomain=$(_getfield "$entry" 2)
3951 txtdomain=$(_idn "$txtdomain")
3952 aliasDomain=$(_getfield "$entry" 3)
3953 aliasDomain=$(_idn "$aliasDomain")
3954 txt=$(_getfield "$entry" 5)
3955 d_api=$(_getfield "$entry" 6)
3956 _debug "d" "$d"
3957 _debug "txtdomain" "$txtdomain"
3958 _debug "aliasDomain" "$aliasDomain"
3959 _debug "txt" "$txt"
3960 _debug "d_api" "$d_api"
3961 _info "Checking $d for $aliasDomain"
3962 if _contains "$_success_txt" ",$txt,"; then
3963 _info "Already success, continue next one."
3964 continue
3965 fi
3966
3967 if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then
3968 _info "Domain $d '$aliasDomain' success."
3969 _success_txt="$_success_txt,$txt,"
3970 continue
3971 fi
3972 _left=1
3973 _info "Not valid yet, let's wait 10 seconds and check next one."
3974 __purge_txt "$txtdomain"
3975 if [ "$txtdomain" != "$aliasDomain" ]; then
3976 __purge_txt "$aliasDomain"
3977 fi
3978 _sleep 10
3979 done
3980 if [ "$_left" ]; then
3981 _info "Let's wait 10 seconds and check again".
3982 _sleep 10
3983 else
3984 _info "All success, let's return"
3985 return 0
3986 fi
3987 done
3988 _info "Timed out waiting for DNS."
3989 return 1
3990
3991 }
3992
3993 #file
3994 _get_cert_issuers() {
3995 _cfile="$1"
3996 if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7"; then
3997 ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
3998 else
3999 ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
4000 fi
4001 }
4002
4003 #cert issuer
4004 _match_issuer() {
4005 _cfile="$1"
4006 _missuer="$2"
4007 _fissuers="$(_get_cert_issuers $_cfile)"
4008 _debug2 _fissuers "$_fissuers"
4009 _contains "$_fissuers" "$_missuer"
4010 }
4011
4012 #webroot, domain domainlist keylength
4013 issue() {
4014 if [ -z "$2" ]; then
4015 _usage "Usage: $PROJECT_ENTRY --issue -d a.com -w /path/to/webroot/a.com/ "
4016 return 1
4017 fi
4018 if [ -z "$1" ]; then
4019 _usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc."
4020 return 1
4021 fi
4022 _web_roots="$1"
4023 _main_domain="$2"
4024 _alt_domains="$3"
4025
4026 if _contains "$_main_domain" ","; then
4027 _main_domain=$(echo "$2,$3" | cut -d , -f 1)
4028 _alt_domains=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//")
4029 fi
4030 _debug _main_domain "$_main_domain"
4031 _debug _alt_domains "$_alt_domains"
4032
4033 _key_length="$4"
4034 _real_cert="$5"
4035 _real_key="$6"
4036 _real_ca="$7"
4037 _reload_cmd="$8"
4038 _real_fullchain="$9"
4039 _pre_hook="${10}"
4040 _post_hook="${11}"
4041 _renew_hook="${12}"
4042 _local_addr="${13}"
4043 _challenge_alias="${14}"
4044 _preferred_chain="${15}"
4045
4046 if [ -z "$_ACME_IS_RENEW" ]; then
4047 _initpath "$_main_domain" "$_key_length"
4048 mkdir -p "$DOMAIN_PATH"
4049 fi
4050
4051 if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
4052 _err "$_DNS_MANUAL_ERROR"
4053 return 1
4054 fi
4055
4056 _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
4057
4058 _initAPI
4059
4060 if [ -f "$DOMAIN_CONF" ]; then
4061 Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)
4062 _debug Le_NextRenewTime "$Le_NextRenewTime"
4063 if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
4064 _saved_domain=$(_readdomainconf Le_Domain)
4065 _debug _saved_domain "$_saved_domain"
4066 _saved_alt=$(_readdomainconf Le_Alt)
4067 _debug _saved_alt "$_saved_alt"
4068 if [ "$_saved_domain,$_saved_alt" = "$_main_domain,$_alt_domains" ]; then
4069 _info "Domains not changed."
4070 _info "Skip, Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")"
4071 _info "Add '$(__red '--force')' to force to renew."
4072 return $RENEW_SKIP
4073 else
4074 _info "Domains have changed."
4075 fi
4076 fi
4077 fi
4078
4079 _savedomainconf "Le_Domain" "$_main_domain"
4080 _savedomainconf "Le_Alt" "$_alt_domains"
4081 _savedomainconf "Le_Webroot" "$_web_roots"
4082
4083 _savedomainconf "Le_PreHook" "$_pre_hook" "base64"
4084 _savedomainconf "Le_PostHook" "$_post_hook" "base64"
4085 _savedomainconf "Le_RenewHook" "$_renew_hook" "base64"
4086
4087 if [ "$_local_addr" ]; then
4088 _savedomainconf "Le_LocalAddress" "$_local_addr"
4089 else
4090 _cleardomainconf "Le_LocalAddress"
4091 fi
4092 if [ "$_challenge_alias" ]; then
4093 _savedomainconf "Le_ChallengeAlias" "$_challenge_alias"
4094 else
4095 _cleardomainconf "Le_ChallengeAlias"
4096 fi
4097 if [ "$_preferred_chain" ]; then
4098 _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64"
4099 else
4100 _cleardomainconf "Le_Preferred_Chain"
4101 fi
4102
4103 Le_API="$ACME_DIRECTORY"
4104 _savedomainconf "Le_API" "$Le_API"
4105
4106 _info "Using CA: $ACME_DIRECTORY"
4107 if [ "$_alt_domains" = "$NO_VALUE" ]; then
4108 _alt_domains=""
4109 fi
4110
4111 if [ "$_key_length" = "$NO_VALUE" ]; then
4112 _key_length=""
4113 fi
4114
4115 if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then
4116 _err "_on_before_issue."
4117 return 1
4118 fi
4119
4120 _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
4121 _debug2 _saved_account_key_hash "$_saved_account_key_hash"
4122
4123 if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
4124 if ! _regAccount "$_accountkeylength"; then
4125 _on_issue_err "$_post_hook"
4126 return 1
4127 fi
4128 else
4129 _debug "_saved_account_key_hash is not changed, skip register account."
4130 fi
4131
4132 if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then
4133 _info "Signing from existing CSR."
4134 else
4135 _key=$(_readdomainconf Le_Keylength)
4136 _debug "Read key length:$_key"
4137 if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then
4138 if ! createDomainKey "$_main_domain" "$_key_length"; then
4139 _err "Create domain key error."
4140 _clearup
4141 _on_issue_err "$_post_hook"
4142 return 1
4143 fi
4144 fi
4145
4146 if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then
4147 _err "Create CSR error."
4148 _clearup
4149 _on_issue_err "$_post_hook"
4150 return 1
4151 fi
4152 fi
4153
4154 _savedomainconf "Le_Keylength" "$_key_length"
4155
4156 vlist="$Le_Vlist"
4157 _cleardomainconf "Le_Vlist"
4158 _info "Getting domain auth token for each domain"
4159 sep='#'
4160 dvsep=','
4161 if [ -z "$vlist" ]; then
4162 if [ "$ACME_VERSION" = "2" ]; then
4163 #make new order request
4164 _identifiers="{\"type\":\"dns\",\"value\":\"$(_idn "$_main_domain")\"}"
4165 _w_index=1
4166 while true; do
4167 d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")"
4168 _w_index="$(_math "$_w_index" + 1)"
4169 _debug d "$d"
4170 if [ -z "$d" ]; then
4171 break
4172 fi
4173 _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$(_idn "$d")\"}"
4174 done
4175 _debug2 _identifiers "$_identifiers"
4176 if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
4177 _err "Create new order error."
4178 _clearup
4179 _on_issue_err "$_post_hook"
4180 return 1
4181 fi
4182 Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
4183 _debug Le_LinkOrder "$Le_LinkOrder"
4184 Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
4185 _debug Le_OrderFinalize "$Le_OrderFinalize"
4186 if [ -z "$Le_OrderFinalize" ]; then
4187 _err "Create new order error. Le_OrderFinalize not found. $response"
4188 _clearup
4189 _on_issue_err "$_post_hook"
4190 return 1
4191 fi
4192
4193 #for dns manual mode
4194 _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
4195
4196 _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
4197 _debug2 _authorizations_seg "$_authorizations_seg"
4198 if [ -z "$_authorizations_seg" ]; then
4199 _err "_authorizations_seg not found."
4200 _clearup
4201 _on_issue_err "$_post_hook"
4202 return 1
4203 fi
4204
4205 #domain and authz map
4206 _authorizations_map=""
4207 for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
4208 _debug2 "_authz_url" "$_authz_url"
4209 if ! _send_signed_request "$_authz_url"; then
4210 _err "get to authz error."
4211 _err "_authorizations_seg" "$_authorizations_seg"
4212 _err "_authz_url" "$_authz_url"
4213 _clearup
4214 _on_issue_err "$_post_hook"
4215 return 1
4216 fi
4217
4218 response="$(echo "$response" | _normalizeJson)"
4219 _debug2 response "$response"
4220 _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2 | tr -d ' "')"
4221 if _contains "$response" "\"wildcard\" *: *true"; then
4222 _d="*.$_d"
4223 fi
4224 _debug2 _d "$_d"
4225 _authorizations_map="$_d,$response
4226 $_authorizations_map"
4227 done
4228 _debug2 _authorizations_map "$_authorizations_map"
4229 fi
4230
4231 _index=0
4232 _currentRoot=""
4233 _w_index=1
4234 while true; do
4235 d="$(echo "$_main_domain,$_alt_domains," | cut -d , -f "$_w_index")"
4236 _w_index="$(_math "$_w_index" + 1)"
4237 _debug d "$d"
4238 if [ -z "$d" ]; then
4239 break
4240 fi
4241 _info "Getting webroot for domain" "$d"
4242 _index=$(_math $_index + 1)
4243 _w="$(echo $_web_roots | cut -d , -f $_index)"
4244 _debug _w "$_w"
4245 if [ "$_w" ]; then
4246 _currentRoot="$_w"
4247 fi
4248 _debug "_currentRoot" "$_currentRoot"
4249
4250 vtype="$VTYPE_HTTP"
4251 #todo, v2 wildcard force to use dns
4252 if _startswith "$_currentRoot" "$W_DNS"; then
4253 vtype="$VTYPE_DNS"
4254 fi
4255
4256 if [ "$_currentRoot" = "$W_ALPN" ]; then
4257 vtype="$VTYPE_ALPN"
4258 fi
4259
4260 if [ "$ACME_VERSION" = "2" ]; then
4261 _idn_d="$(_idn "$d")"
4262 _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
4263 _debug2 _candidates "$_candidates"
4264 if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then
4265 for _can in $_candidates; do
4266 if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
4267 _candidates="$_can"
4268 break
4269 fi
4270 done
4271 fi
4272 response="$(echo "$_candidates" | sed "s/$_idn_d,//")"
4273 _debug2 "response" "$response"
4274 if [ -z "$response" ]; then
4275 _err "get to authz error."
4276 _err "_authorizations_map" "$_authorizations_map"
4277 _clearup
4278 _on_issue_err "$_post_hook"
4279 return 1
4280 fi
4281 else
4282 if ! __get_domain_new_authz "$d"; then
4283 _clearup
4284 _on_issue_err "$_post_hook"
4285 return 1
4286 fi
4287 fi
4288
4289 if [ -z "$thumbprint" ]; then
4290 thumbprint="$(__calc_account_thumbprint)"
4291 fi
4292
4293 entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
4294 _debug entry "$entry"
4295 keyauthorization=""
4296 if [ -z "$entry" ]; then
4297 if ! _startswith "$d" '*.'; then
4298 _debug "Not a wildcard domain, lets check whether the validation is already valid."
4299 if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
4300 _debug "$d is already valid."
4301 keyauthorization="$STATE_VERIFIED"
4302 _debug keyauthorization "$keyauthorization"
4303 fi
4304 fi
4305 if [ -z "$keyauthorization" ]; then
4306 _err "Error, can not get domain token entry $d for $vtype"
4307 _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
4308 if [ "$_supported_vtypes" ]; then
4309 _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
4310 fi
4311 _clearup
4312 _on_issue_err "$_post_hook"
4313 return 1
4314 fi
4315 fi
4316
4317 if [ -z "$keyauthorization" ]; then
4318 token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
4319 _debug token "$token"
4320
4321 if [ -z "$token" ]; then
4322 _err "Error, can not get domain token $entry"
4323 _clearup
4324 _on_issue_err "$_post_hook"
4325 return 1
4326 fi
4327 if [ "$ACME_VERSION" = "2" ]; then
4328 uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
4329 else
4330 uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
4331 fi
4332 _debug uri "$uri"
4333
4334 if [ -z "$uri" ]; then
4335 _err "Error, can not get domain uri. $entry"
4336 _clearup
4337 _on_issue_err "$_post_hook"
4338 return 1
4339 fi
4340 keyauthorization="$token.$thumbprint"
4341 _debug keyauthorization "$keyauthorization"
4342
4343 if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
4344 _debug "$d is already verified."
4345 keyauthorization="$STATE_VERIFIED"
4346 _debug keyauthorization "$keyauthorization"
4347 fi
4348 fi
4349
4350 dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
4351 _debug dvlist "$dvlist"
4352
4353 vlist="$vlist$dvlist$dvsep"
4354
4355 done
4356 _debug vlist "$vlist"
4357 #add entry
4358 dns_entries=""
4359 dnsadded=""
4360 ventries=$(echo "$vlist" | tr "$dvsep" ' ')
4361 _alias_index=1
4362 for ventry in $ventries; do
4363 d=$(echo "$ventry" | cut -d "$sep" -f 1)
4364 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
4365 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
4366 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
4367 _debug d "$d"
4368 if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
4369 _debug "$d is already verified, skip $vtype."
4370 _alias_index="$(_math "$_alias_index" + 1)"
4371 continue
4372 fi
4373
4374 if [ "$vtype" = "$VTYPE_DNS" ]; then
4375 dnsadded='0'
4376 _dns_root_d="$d"
4377 if _startswith "$_dns_root_d" "*."; then
4378 _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')"
4379 fi
4380 _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
4381 _alias_index="$(_math "$_alias_index" + 1)"
4382 _debug "_d_alias" "$_d_alias"
4383 if [ "$_d_alias" ]; then
4384 if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then
4385 txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")"
4386 else
4387 txtdomain="_acme-challenge.$_d_alias"
4388 fi
4389 dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$txtdomain$dvsep$_currentRoot"
4390 else
4391 txtdomain="_acme-challenge.$_dns_root_d"
4392 dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$dvsep$_currentRoot"
4393 fi
4394
4395 _debug txtdomain "$txtdomain"
4396 txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)"
4397 _debug txt "$txt"
4398
4399 d_api="$(_findHook "$_dns_root_d" $_SUB_FOLDER_DNSAPI "$_currentRoot")"
4400 _debug d_api "$d_api"
4401
4402 dns_entry="$dns_entry$dvsep$txt${dvsep}$d_api"
4403 _debug2 dns_entry "$dns_entry"
4404 if [ "$d_api" ]; then
4405 _debug "Found domain api file: $d_api"
4406 else
4407 if [ "$_currentRoot" != "$W_DNS" ]; then
4408 _err "Can not find dns api hook for: $_currentRoot"
4409 _info "You need to add the txt record manually."
4410 fi
4411 _info "$(__red "Add the following TXT record:")"
4412 _info "$(__red "Domain: '$(__green "$txtdomain")'")"
4413 _info "$(__red "TXT value: '$(__green "$txt")'")"
4414 _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")"
4415 _info "$(__red "so the resulting subdomain will be: $txtdomain")"
4416 continue
4417 fi
4418
4419 (
4420 if ! . "$d_api"; then
4421 _err "Load file $d_api error. Please check your api file and try again."
4422 return 1
4423 fi
4424
4425 addcommand="${_currentRoot}_add"
4426 if ! _exists "$addcommand"; then
4427 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
4428 return 1
4429 fi
4430 _info "Adding txt value: $txt for domain: $txtdomain"
4431 if ! $addcommand "$txtdomain" "$txt"; then
4432 _err "Error add txt for domain:$txtdomain"
4433 return 1
4434 fi
4435 _info "The txt record is added: Success."
4436 )
4437
4438 if [ "$?" != "0" ]; then
4439 _on_issue_err "$_post_hook" "$vlist"
4440 _clearup
4441 return 1
4442 fi
4443 dns_entries="$dns_entries$dns_entry
4444 "
4445 _debug2 "$dns_entries"
4446 dnsadded='1'
4447 fi
4448 done
4449
4450 if [ "$dnsadded" = '0' ]; then
4451 _savedomainconf "Le_Vlist" "$vlist"
4452 _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
4453 _err "Please add the TXT records to the domains, and re-run with --renew."
4454 _on_issue_err "$_post_hook"
4455 _clearup
4456 return 1
4457 fi
4458
4459 fi
4460
4461 if [ "$dns_entries" ]; then
4462 if [ -z "$Le_DNSSleep" ]; then
4463 _info "Let's check each DNS record now. Sleep 20 seconds first."
4464 _sleep 20
4465 if ! _check_dns_entries; then
4466 _err "check dns error."
4467 _on_issue_err "$_post_hook"
4468 _clearup
4469 return 1
4470 fi
4471 else
4472 _savedomainconf "Le_DNSSleep" "$Le_DNSSleep"
4473 _info "Sleep $(__green $Le_DNSSleep) seconds for the txt records to take effect"
4474 _sleep "$Le_DNSSleep"
4475 fi
4476 fi
4477
4478 NGINX_RESTORE_VLIST=""
4479 _debug "ok, let's start to verify"
4480
4481 _ncIndex=1
4482 ventries=$(echo "$vlist" | tr "$dvsep" ' ')
4483 for ventry in $ventries; do
4484 d=$(echo "$ventry" | cut -d "$sep" -f 1)
4485 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
4486 uri=$(echo "$ventry" | cut -d "$sep" -f 3)
4487 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
4488 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
4489
4490 if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
4491 _info "$d is already verified, skip $vtype."
4492 continue
4493 fi
4494
4495 _info "Verifying: $d"
4496 _debug "d" "$d"
4497 _debug "keyauthorization" "$keyauthorization"
4498 _debug "uri" "$uri"
4499 removelevel=""
4500 token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)"
4501
4502 _debug "_currentRoot" "$_currentRoot"
4503
4504 if [ "$vtype" = "$VTYPE_HTTP" ]; then
4505 if [ "$_currentRoot" = "$NO_VALUE" ]; then
4506 _info "Standalone mode server"
4507 _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
4508 _ncIndex="$(_math $_ncIndex + 1)"
4509 _startserver "$keyauthorization" "$_ncaddr"
4510 if [ "$?" != "0" ]; then
4511 _clearup
4512 _on_issue_err "$_post_hook" "$vlist"
4513 return 1
4514 fi
4515 sleep 1
4516 _debug serverproc "$serverproc"
4517 elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then
4518 _info "Stateless mode for domain:$d"
4519 _sleep 1
4520 elif _startswith "$_currentRoot" "$NGINX"; then
4521 _info "Nginx mode for domain:$d"
4522 #set up nginx server
4523 FOUND_REAL_NGINX_CONF=""
4524 BACKUP_NGINX_CONF=""
4525 if ! _setNginx "$d" "$_currentRoot" "$thumbprint"; then
4526 _clearup
4527 _on_issue_err "$_post_hook" "$vlist"
4528 return 1
4529 fi
4530
4531 if [ "$FOUND_REAL_NGINX_CONF" ]; then
4532 _realConf="$FOUND_REAL_NGINX_CONF"
4533 _backup="$BACKUP_NGINX_CONF"
4534 _debug _realConf "$_realConf"
4535 NGINX_RESTORE_VLIST="$d$sep$_realConf$sep$_backup$dvsep$NGINX_RESTORE_VLIST"
4536 fi
4537 _sleep 1
4538 else
4539 if [ "$_currentRoot" = "apache" ]; then
4540 wellknown_path="$ACME_DIR"
4541 else
4542 wellknown_path="$_currentRoot/.well-known/acme-challenge"
4543 if [ ! -d "$_currentRoot/.well-known" ]; then
4544 removelevel='1'
4545 elif [ ! -d "$_currentRoot/.well-known/acme-challenge" ]; then
4546 removelevel='2'
4547 else
4548 removelevel='3'
4549 fi
4550 fi
4551
4552 _debug wellknown_path "$wellknown_path"
4553
4554 _debug "writing token:$token to $wellknown_path/$token"
4555
4556 mkdir -p "$wellknown_path"
4557
4558 if ! printf "%s" "$keyauthorization" >"$wellknown_path/$token"; then
4559 _err "$d:Can not write token to file : $wellknown_path/$token"
4560 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4561 _clearup
4562 _on_issue_err "$_post_hook" "$vlist"
4563 return 1
4564 fi
4565
4566 if [ ! "$usingApache" ]; then
4567 if webroot_owner=$(_stat "$_currentRoot"); then
4568 _debug "Changing owner/group of .well-known to $webroot_owner"
4569 if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then
4570 _debug "$(cat "$_EXEC_TEMP_ERR")"
4571 _exec_err >/dev/null 2>&1
4572 fi
4573 else
4574 _debug "not changing owner/group of webroot"
4575 fi
4576 fi
4577
4578 fi
4579 elif [ "$vtype" = "$VTYPE_ALPN" ]; then
4580 acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
4581 _debug acmevalidationv1 "$acmevalidationv1"
4582 if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then
4583 _err "Start tls server error."
4584 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4585 _clearup
4586 _on_issue_err "$_post_hook" "$vlist"
4587 return 1
4588 fi
4589 fi
4590
4591 if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then
4592 _err "$d:Can not get challenge: $response"
4593 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4594 _clearup
4595 _on_issue_err "$_post_hook" "$vlist"
4596 return 1
4597 fi
4598
4599 if [ "$code" ] && [ "$code" != '202' ]; then
4600 if [ "$code" = '200' ]; then
4601 _debug "trigger validation code: $code"
4602 else
4603 _err "$d:Challenge error: $response"
4604 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4605 _clearup
4606 _on_issue_err "$_post_hook" "$vlist"
4607 return 1
4608 fi
4609 fi
4610
4611 waittimes=0
4612 if [ -z "$MAX_RETRY_TIMES" ]; then
4613 MAX_RETRY_TIMES=30
4614 fi
4615
4616 while true; do
4617 waittimes=$(_math "$waittimes" + 1)
4618 if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then
4619 _err "$d:Timeout"
4620 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4621 _clearup
4622 _on_issue_err "$_post_hook" "$vlist"
4623 return 1
4624 fi
4625
4626 _debug "sleep 2 secs to verify"
4627 sleep 2
4628 _debug "checking"
4629 if [ "$ACME_VERSION" = "2" ]; then
4630 _send_signed_request "$uri"
4631 else
4632 response="$(_get "$uri")"
4633 fi
4634 if [ "$?" != "0" ]; then
4635 _err "$d:Verify error:$response"
4636 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4637 _clearup
4638 _on_issue_err "$_post_hook" "$vlist"
4639 return 1
4640 fi
4641 _debug2 original "$response"
4642
4643 response="$(echo "$response" | _normalizeJson)"
4644 _debug2 response "$response"
4645
4646 status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
4647 if [ "$status" = "valid" ]; then
4648 _info "$(__green Success)"
4649 _stopserver "$serverproc"
4650 serverproc=""
4651 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4652 break
4653 fi
4654
4655 if [ "$status" = "invalid" ]; then
4656 error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
4657 _debug2 error "$error"
4658 errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
4659 _debug2 errordetail "$errordetail"
4660 if [ "$errordetail" ]; then
4661 _err "$d:Verify error:$errordetail"
4662 else
4663 _err "$d:Verify error:$error"
4664 fi
4665 if [ "$DEBUG" ]; then
4666 if [ "$vtype" = "$VTYPE_HTTP" ]; then
4667 _debug "Debug: get token url."
4668 _get "http://$d/.well-known/acme-challenge/$token" "" 1
4669 fi
4670 fi
4671 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4672 _clearup
4673 _on_issue_err "$_post_hook" "$vlist"
4674 return 1
4675 fi
4676
4677 if [ "$status" = "pending" ]; then
4678 _info "Pending"
4679 elif [ "$status" = "processing" ]; then
4680 _info "Processing"
4681 else
4682 _err "$d:Verify error:$response"
4683 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4684 _clearup
4685 _on_issue_err "$_post_hook" "$vlist"
4686 return 1
4687 fi
4688
4689 done
4690
4691 done
4692
4693 _clearup
4694 _info "Verify finished, start to sign."
4695 der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
4696
4697 if [ "$ACME_VERSION" = "2" ]; then
4698 _info "Lets finalize the order."
4699 _info "Le_OrderFinalize" "$Le_OrderFinalize"
4700 if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
4701 _err "Sign failed."
4702 _on_issue_err "$_post_hook"
4703 return 1
4704 fi
4705 if [ "$code" != "200" ]; then
4706 _err "Sign failed, finalize code is not 200."
4707 _err "$response"
4708 _on_issue_err "$_post_hook"
4709 return 1
4710 fi
4711 if [ -z "$Le_LinkOrder" ]; then
4712 Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d ":" -f 2-)"
4713 fi
4714
4715 _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
4716
4717 _link_cert_retry=0
4718 _MAX_CERT_RETRY=30
4719 while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
4720 if _contains "$response" "\"status\":\"valid\""; then
4721 _debug "Order status is valid."
4722 Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
4723 _debug Le_LinkCert "$Le_LinkCert"
4724 if [ -z "$Le_LinkCert" ]; then
4725 _err "Sign error, can not find Le_LinkCert"
4726 _err "$response"
4727 _on_issue_err "$_post_hook"
4728 return 1
4729 fi
4730 break
4731 elif _contains "$response" "\"processing\""; then
4732 _info "Order status is processing, lets sleep and retry."
4733 _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
4734 _debug "_retryafter" "$_retryafter"
4735 if [ "$_retryafter" ]; then
4736 _info "Retry after: $_retryafter"
4737 _sleep $_retryafter
4738 else
4739 _sleep 2
4740 fi
4741 else
4742 _err "Sign error, wrong status"
4743 _err "$response"
4744 _on_issue_err "$_post_hook"
4745 return 1
4746 fi
4747 #the order is processing, so we are going to poll order status
4748 if [ -z "$Le_LinkOrder" ]; then
4749 _err "Sign error, can not get order link location header"
4750 _err "responseHeaders" "$responseHeaders"
4751 _on_issue_err "$_post_hook"
4752 return 1
4753 fi
4754 _info "Polling order status: $Le_LinkOrder"
4755 if ! _send_signed_request "$Le_LinkOrder"; then
4756 _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
4757 _err "$response"
4758 _on_issue_err "$_post_hook"
4759 return 1
4760 fi
4761 _link_cert_retry="$(_math $_link_cert_retry + 1)"
4762 done
4763
4764 if [ -z "$Le_LinkCert" ]; then
4765 _err "Sign failed, can not get Le_LinkCert, retry time limit."
4766 _err "$response"
4767 _on_issue_err "$_post_hook"
4768 return 1
4769 fi
4770 _info "Downloading cert."
4771 _info "Le_LinkCert" "$Le_LinkCert"
4772 if ! _send_signed_request "$Le_LinkCert"; then
4773 _err "Sign failed, can not download cert:$Le_LinkCert."
4774 _err "$response"
4775 _on_issue_err "$_post_hook"
4776 return 1
4777 fi
4778
4779 echo "$response" >"$CERT_PATH"
4780 _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
4781
4782 if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then
4783 if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then
4784 rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)"
4785 _debug2 "rels" "$rels"
4786 for rel in $rels; do
4787 _info "Try rel: $rel"
4788 if ! _send_signed_request "$rel"; then
4789 _err "Sign failed, can not download cert:$rel"
4790 _err "$response"
4791 continue
4792 fi
4793 _relcert="$CERT_PATH.alt"
4794 _relfullchain="$CERT_FULLCHAIN_PATH.alt"
4795 _relca="$CA_CERT_PATH.alt"
4796 echo "$response" >"$_relcert"
4797 _split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
4798 if _match_issuer "$_relfullchain" "$_preferred_chain"; then
4799 _info "Matched issuer in: $rel"
4800 cat $_relcert >"$CERT_PATH"
4801 cat $_relfullchain >"$CERT_FULLCHAIN_PATH"
4802 cat $_relca >"$CA_CERT_PATH"
4803 break
4804 fi
4805 done
4806 fi
4807 fi
4808 else
4809 if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
4810 _err "Sign failed. $response"
4811 _on_issue_err "$_post_hook"
4812 return 1
4813 fi
4814 _rcert="$response"
4815 Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
4816 echo "$BEGIN_CERT" >"$CERT_PATH"
4817
4818 #if ! _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH" ; then
4819 # _debug "Get cert failed. Let's try last response."
4820 # printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH"
4821 #fi
4822
4823 if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
4824 _debug "Try cert link."
4825 _get "$Le_LinkCert" | _base64 "multiline" >>"$CERT_PATH"
4826 fi
4827
4828 echo "$END_CERT" >>"$CERT_PATH"
4829 fi
4830
4831 _debug "Le_LinkCert" "$Le_LinkCert"
4832 _savedomainconf "Le_LinkCert" "$Le_LinkCert"
4833
4834 if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then
4835 response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)"
4836 _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')"
4837 _on_issue_err "$_post_hook"
4838 return 1
4839 fi
4840
4841 if [ "$Le_LinkCert" ]; then
4842 _info "$(__green "Cert success.")"
4843 cat "$CERT_PATH"
4844
4845 _info "Your cert is in $(__green " $CERT_PATH ")"
4846
4847 if [ -f "$CERT_KEY_PATH" ]; then
4848 _info "Your cert key is in $(__green " $CERT_KEY_PATH ")"
4849 fi
4850
4851 if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then
4852 USER_PATH="$PATH"
4853 _saveaccountconf "USER_PATH" "$USER_PATH"
4854 fi
4855 fi
4856
4857 if [ "$ACME_VERSION" = "2" ]; then
4858 _debug "v2 chain."
4859 else
4860 cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
4861 Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>')
4862
4863 if [ "$Le_LinkIssuer" ]; then
4864 if ! _contains "$Le_LinkIssuer" ":"; then
4865 _info "$(__red "Relative issuer link found.")"
4866 Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer"
4867 fi
4868 _debug Le_LinkIssuer "$Le_LinkIssuer"
4869 _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
4870
4871 _link_issuer_retry=0
4872 _MAX_ISSUER_RETRY=5
4873 while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
4874 _debug _link_issuer_retry "$_link_issuer_retry"
4875 if [ "$ACME_VERSION" = "2" ]; then
4876 if _send_signed_request "$Le_LinkIssuer"; then
4877 echo "$response" >"$CA_CERT_PATH"
4878 break
4879 fi
4880 else
4881 if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then
4882 echo "$BEGIN_CERT" >"$CA_CERT_PATH"
4883 _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH"
4884 echo "$END_CERT" >>"$CA_CERT_PATH"
4885 if ! _checkcert "$CA_CERT_PATH"; then
4886 _err "Can not get the ca cert."
4887 break
4888 fi
4889 cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
4890 rm -f "$CA_CERT_PATH.der"
4891 break
4892 fi
4893 fi
4894 _link_issuer_retry=$(_math $_link_issuer_retry + 1)
4895 _sleep "$_link_issuer_retry"
4896 done
4897 if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then
4898 _err "Max retry for issuer ca cert is reached."
4899 fi
4900 else
4901 _debug "No Le_LinkIssuer header found."
4902 fi
4903 fi
4904 [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")"
4905 [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
4906
4907 Le_CertCreateTime=$(_time)
4908 _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
4909
4910 Le_CertCreateTimeStr=$(date -u)
4911 _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
4912
4913 if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then
4914 Le_RenewalDays="$DEFAULT_RENEW"
4915 else
4916 _savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
4917 fi
4918
4919 if [ "$CA_BUNDLE" ]; then
4920 _saveaccountconf CA_BUNDLE "$CA_BUNDLE"
4921 else
4922 _clearaccountconf "CA_BUNDLE"
4923 fi
4924
4925 if [ "$CA_PATH" ]; then
4926 _saveaccountconf CA_PATH "$CA_PATH"
4927 else
4928 _clearaccountconf "CA_PATH"
4929 fi
4930
4931 if [ "$HTTPS_INSECURE" ]; then
4932 _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
4933 else
4934 _clearaccountconf "HTTPS_INSECURE"
4935 fi
4936
4937 if [ "$Le_Listen_V4" ]; then
4938 _savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
4939 _cleardomainconf Le_Listen_V6
4940 elif [ "$Le_Listen_V6" ]; then
4941 _savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
4942 _cleardomainconf Le_Listen_V4
4943 fi
4944
4945 if [ "$Le_ForceNewDomainKey" = "1" ]; then
4946 _savedomainconf "Le_ForceNewDomainKey" "$Le_ForceNewDomainKey"
4947 else
4948 _cleardomainconf Le_ForceNewDomainKey
4949 fi
4950
4951 Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
4952
4953 Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
4954 _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
4955
4956 Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
4957 _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
4958
4959 if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
4960 _savedomainconf "Le_RealCertPath" "$_real_cert"
4961 _savedomainconf "Le_RealCACertPath" "$_real_ca"
4962 _savedomainconf "Le_RealKeyPath" "$_real_key"
4963 _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
4964 _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
4965 if ! _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
4966 return 1
4967 fi
4968 fi
4969
4970 if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
4971 _err "Call hook error."
4972 return 1
4973 fi
4974 }
4975
4976 #in_out_cert out_fullchain out out_ca
4977 _split_cert_chain() {
4978 _certf="$1"
4979 _fullchainf="$2"
4980 _caf="$3"
4981 if [ "$(grep -- "$BEGIN_CERT" "$_certf" | wc -l)" -gt "1" ]; then
4982 _debug "Found cert chain"
4983 cat "$_certf" >"$_fullchainf"
4984 _end_n="$(grep -n -- "$END_CERT" "$_fullchainf" | _head_n 1 | cut -d : -f 1)"
4985 _debug _end_n "$_end_n"
4986 sed -n "1,${_end_n}p" "$_fullchainf" >"$_certf"
4987 _end_n="$(_math $_end_n + 1)"
4988 sed -n "${_end_n},9999p" "$_fullchainf" >"$_caf"
4989 fi
4990 }
4991
4992 #domain [isEcc]
4993 renew() {
4994 Le_Domain="$1"
4995 if [ -z "$Le_Domain" ]; then
4996 _usage "Usage: $PROJECT_ENTRY --renew -d domain.com [--ecc]"
4997 return 1
4998 fi
4999
5000 _isEcc="$2"
5001
5002 _initpath "$Le_Domain" "$_isEcc"
5003
5004 _info "$(__green "Renew: '$Le_Domain'")"
5005 if [ ! -f "$DOMAIN_CONF" ]; then
5006 _info "'$Le_Domain' is not a issued domain, skip."
5007 return $RENEW_SKIP
5008 fi
5009
5010 if [ "$Le_RenewalDays" ]; then
5011 _savedomainconf Le_RenewalDays "$Le_RenewalDays"
5012 fi
5013
5014 . "$DOMAIN_CONF"
5015 _debug Le_API "$Le_API"
5016
5017 if [ "$Le_API" = "$LETSENCRYPT_CA_V1" ]; then
5018 _cleardomainconf Le_API
5019 Le_API="$DEFAULT_CA"
5020 fi
5021 if [ "$Le_API" = "$LETSENCRYPT_STAGING_CA_V1" ]; then
5022 _cleardomainconf Le_API
5023 Le_API="$DEFAULT_STAGING_CA"
5024 fi
5025
5026 if [ "$Le_API" ]; then
5027 export ACME_DIRECTORY="$Le_API"
5028 #reload ca configs
5029 ACCOUNT_KEY_PATH=""
5030 ACCOUNT_JSON_PATH=""
5031 CA_CONF=""
5032 _debug3 "initpath again."
5033 _initpath "$Le_Domain" "$_isEcc"
5034 fi
5035
5036 if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
5037 _info "Skip, Next renewal time is: $(__green "$Le_NextRenewTimeStr")"
5038 _info "Add '$(__red '--force')' to force to renew."
5039 return "$RENEW_SKIP"
5040 fi
5041
5042 if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then
5043 _info "Skip invalid cert for: $Le_Domain"
5044 return $RENEW_SKIP
5045 fi
5046
5047 _ACME_IS_RENEW="1"
5048 Le_ReloadCmd="$(_readdomainconf Le_ReloadCmd)"
5049 Le_PreHook="$(_readdomainconf Le_PreHook)"
5050 Le_PostHook="$(_readdomainconf Le_PostHook)"
5051 Le_RenewHook="$(_readdomainconf Le_RenewHook)"
5052 Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)"
5053 issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain"
5054 res="$?"
5055 if [ "$res" != "0" ]; then
5056 return "$res"
5057 fi
5058
5059 if [ "$Le_DeployHook" ]; then
5060 _deploy "$Le_Domain" "$Le_DeployHook"
5061 res="$?"
5062 fi
5063
5064 _ACME_IS_RENEW=""
5065
5066 return "$res"
5067 }
5068
5069 #renewAll [stopRenewOnError]
5070 renewAll() {
5071 _initpath
5072 _stopRenewOnError="$1"
5073 _debug "_stopRenewOnError" "$_stopRenewOnError"
5074 _ret="0"
5075 _success_msg=""
5076 _error_msg=""
5077 _skipped_msg=""
5078 _error_level=$NOTIFY_LEVEL_SKIP
5079 _notify_code=$RENEW_SKIP
5080 _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
5081 _debug "_set_level" "$_set_level"
5082 for di in "${CERT_HOME}"/*.*/; do
5083 _debug di "$di"
5084 if ! [ -d "$di" ]; then
5085 _debug "Not directory, skip: $di"
5086 continue
5087 fi
5088 d=$(basename "$di")
5089 _debug d "$d"
5090 (
5091 if _endswith "$d" "$ECC_SUFFIX"; then
5092 _isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2)
5093 d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
5094 fi
5095 renew "$d" "$_isEcc"
5096 )
5097 rc="$?"
5098 _debug "Return code: $rc"
5099 if [ "$rc" = "0" ]; then
5100 if [ $_error_level -gt $NOTIFY_LEVEL_RENEW ]; then
5101 _error_level="$NOTIFY_LEVEL_RENEW"
5102 _notify_code=0
5103 fi
5104 if [ "$_ACME_IN_CRON" ]; then
5105 if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then
5106 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5107 _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0
5108 fi
5109 fi
5110 fi
5111 _success_msg="${_success_msg} $d
5112 "
5113 elif [ "$rc" = "$RENEW_SKIP" ]; then
5114 if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then
5115 _error_level="$NOTIFY_LEVEL_SKIP"
5116 _notify_code=$RENEW_SKIP
5117 fi
5118 if [ "$_ACME_IN_CRON" ]; then
5119 if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then
5120 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5121 _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP"
5122 fi
5123 fi
5124 fi
5125 _info "Skipped $d"
5126 _skipped_msg="${_skipped_msg} $d
5127 "
5128 else
5129 if [ $_error_level -gt $NOTIFY_LEVEL_ERROR ]; then
5130 _error_level="$NOTIFY_LEVEL_ERROR"
5131 _notify_code=1
5132 fi
5133 if [ "$_ACME_IN_CRON" ]; then
5134 if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then
5135 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5136 _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1
5137 fi
5138 fi
5139 fi
5140 _error_msg="${_error_msg} $d
5141 "
5142 if [ "$_stopRenewOnError" ]; then
5143 _err "Error renew $d, stop now."
5144 _ret="$rc"
5145 break
5146 else
5147 _ret="$rc"
5148 _err "Error renew $d."
5149 fi
5150 fi
5151 done
5152 _debug _error_level "$_error_level"
5153 _debug _set_level "$_set_level"
5154 if [ "$_ACME_IN_CRON" ] && [ $_error_level -le $_set_level ]; then
5155 if [ -z "$NOTIFY_MODE" ] || [ "$NOTIFY_MODE" = "$NOTIFY_MODE_BULK" ]; then
5156 _msg_subject="Renew"
5157 if [ "$_error_msg" ]; then
5158 _msg_subject="${_msg_subject} Error"
5159 _msg_data="Error certs:
5160 ${_error_msg}
5161 "
5162 fi
5163 if [ "$_success_msg" ]; then
5164 _msg_subject="${_msg_subject} Success"
5165 _msg_data="${_msg_data}Success certs:
5166 ${_success_msg}
5167 "
5168 fi
5169 if [ "$_skipped_msg" ]; then
5170 _msg_subject="${_msg_subject} Skipped"
5171 _msg_data="${_msg_data}Skipped certs:
5172 ${_skipped_msg}
5173 "
5174 fi
5175
5176 _send_notify "$_msg_subject" "$_msg_data" "$NOTIFY_HOOK" "$_notify_code"
5177 fi
5178 fi
5179
5180 return "$_ret"
5181 }
5182
5183 #csr webroot
5184 signcsr() {
5185 _csrfile="$1"
5186 _csrW="$2"
5187 if [ -z "$_csrfile" ] || [ -z "$_csrW" ]; then
5188 _usage "Usage: $PROJECT_ENTRY --signcsr --csr mycsr.csr -w /path/to/webroot/a.com/ "
5189 return 1
5190 fi
5191
5192 _real_cert="$3"
5193 _real_key="$4"
5194 _real_ca="$5"
5195 _reload_cmd="$6"
5196 _real_fullchain="$7"
5197 _pre_hook="${8}"
5198 _post_hook="${9}"
5199 _renew_hook="${10}"
5200 _local_addr="${11}"
5201 _challenge_alias="${12}"
5202
5203 _csrsubj=$(_readSubjectFromCSR "$_csrfile")
5204 if [ "$?" != "0" ]; then
5205 _err "Can not read subject from csr: $_csrfile"
5206 return 1
5207 fi
5208 _debug _csrsubj "$_csrsubj"
5209 if _contains "$_csrsubj" ' ' || ! _contains "$_csrsubj" '.'; then
5210 _info "It seems that the subject: $_csrsubj is not a valid domain name. Drop it."
5211 _csrsubj=""
5212 fi
5213
5214 _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile")
5215 if [ "$?" != "0" ]; then
5216 _err "Can not read domain list from csr: $_csrfile"
5217 return 1
5218 fi
5219 _debug "_csrdomainlist" "$_csrdomainlist"
5220
5221 if [ -z "$_csrsubj" ]; then
5222 _csrsubj="$(_getfield "$_csrdomainlist" 1)"
5223 _debug _csrsubj "$_csrsubj"
5224 _csrdomainlist="$(echo "$_csrdomainlist" | cut -d , -f 2-)"
5225 _debug "_csrdomainlist" "$_csrdomainlist"
5226 fi
5227
5228 if [ -z "$_csrsubj" ]; then
5229 _err "Can not read subject from csr: $_csrfile"
5230 return 1
5231 fi
5232
5233 _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
5234 if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
5235 _err "Can not read key length from csr: $_csrfile"
5236 return 1
5237 fi
5238
5239 if [ -z "$ACME_VERSION" ] && _contains "$_csrsubj,$_csrdomainlist" "*."; then
5240 export ACME_VERSION=2
5241 fi
5242 _initpath "$_csrsubj" "$_csrkeylength"
5243 mkdir -p "$DOMAIN_PATH"
5244
5245 _info "Copy csr to: $CSR_PATH"
5246 cp "$_csrfile" "$CSR_PATH"
5247
5248 issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias"
5249
5250 }
5251
5252 showcsr() {
5253 _csrfile="$1"
5254 _csrd="$2"
5255 if [ -z "$_csrfile" ] && [ -z "$_csrd" ]; then
5256 _usage "Usage: $PROJECT_ENTRY --showcsr --csr mycsr.csr"
5257 return 1
5258 fi
5259
5260 _initpath
5261
5262 _csrsubj=$(_readSubjectFromCSR "$_csrfile")
5263 if [ "$?" != "0" ] || [ -z "$_csrsubj" ]; then
5264 _err "Can not read subject from csr: $_csrfile"
5265 return 1
5266 fi
5267
5268 _info "Subject=$_csrsubj"
5269
5270 _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile")
5271 if [ "$?" != "0" ]; then
5272 _err "Can not read domain list from csr: $_csrfile"
5273 return 1
5274 fi
5275 _debug "_csrdomainlist" "$_csrdomainlist"
5276
5277 _info "SubjectAltNames=$_csrdomainlist"
5278
5279 _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
5280 if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
5281 _err "Can not read key length from csr: $_csrfile"
5282 return 1
5283 fi
5284 _info "KeyLength=$_csrkeylength"
5285 }
5286
5287 #listraw domain
5288 list() {
5289 _raw="$1"
5290 _domain="$2"
5291 _initpath
5292
5293 _sep="|"
5294 if [ "$_raw" ]; then
5295 if [ -z "$_domain" ]; then
5296 printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew"
5297 fi
5298 for di in "${CERT_HOME}"/*.*/; do
5299 d=$(basename "$di")
5300 _debug d "$d"
5301 (
5302 if _endswith "$d" "$ECC_SUFFIX"; then
5303 _isEcc="ecc"
5304 d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
5305 fi
5306 DOMAIN_CONF="$di/$d.conf"
5307 if [ -f "$DOMAIN_CONF" ]; then
5308 . "$DOMAIN_CONF"
5309 _ca="$(_getCAShortName "$Le_API")"
5310 if [ -z "$_domain" ]; then
5311 printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr"
5312 else
5313 if [ "$_domain" = "$d" ]; then
5314 cat "$DOMAIN_CONF"
5315 fi
5316 fi
5317 fi
5318 )
5319 done
5320 else
5321 if _exists column; then
5322 list "raw" "$_domain" | column -t -s "$_sep"
5323 else
5324 list "raw" "$_domain" | tr "$_sep" '\t'
5325 fi
5326 fi
5327
5328 }
5329
5330 _deploy() {
5331 _d="$1"
5332 _hooks="$2"
5333
5334 for _d_api in $(echo "$_hooks" | tr ',' " "); do
5335 _deployApi="$(_findHook "$_d" $_SUB_FOLDER_DEPLOY "$_d_api")"
5336 if [ -z "$_deployApi" ]; then
5337 _err "The deploy hook $_d_api is not found."
5338 return 1
5339 fi
5340 _debug _deployApi "$_deployApi"
5341
5342 if ! (
5343 if ! . "$_deployApi"; then
5344 _err "Load file $_deployApi error. Please check your api file and try again."
5345 return 1
5346 fi
5347
5348 d_command="${_d_api}_deploy"
5349 if ! _exists "$d_command"; then
5350 _err "It seems that your api file is not correct, it must have a function named: $d_command"
5351 return 1
5352 fi
5353
5354 if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH"; then
5355 _err "Error deploy for domain:$_d"
5356 return 1
5357 fi
5358 ); then
5359 _err "Deploy error."
5360 return 1
5361 else
5362 _info "$(__green Success)"
5363 fi
5364 done
5365 }
5366
5367 #domain hooks
5368 deploy() {
5369 _d="$1"
5370 _hooks="$2"
5371 _isEcc="$3"
5372 if [ -z "$_hooks" ]; then
5373 _usage "Usage: $PROJECT_ENTRY --deploy -d domain.com --deploy-hook cpanel [--ecc] "
5374 return 1
5375 fi
5376
5377 _initpath "$_d" "$_isEcc"
5378 if [ ! -d "$DOMAIN_PATH" ]; then
5379 _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install."
5380 _err "Can not find path:'$DOMAIN_PATH'"
5381 return 1
5382 fi
5383
5384 . "$DOMAIN_CONF"
5385
5386 _savedomainconf Le_DeployHook "$_hooks"
5387
5388 _deploy "$_d" "$_hooks"
5389 }
5390
5391 installcert() {
5392 _main_domain="$1"
5393 if [ -z "$_main_domain" ]; then
5394 _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com [--ecc] [--cert-file cert-file-path] [--key-file key-file-path] [--ca-file ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchain-file fullchain-path]"
5395 return 1
5396 fi
5397
5398 _real_cert="$2"
5399 _real_key="$3"
5400 _real_ca="$4"
5401 _reload_cmd="$5"
5402 _real_fullchain="$6"
5403 _isEcc="$7"
5404
5405 _initpath "$_main_domain" "$_isEcc"
5406 if [ ! -d "$DOMAIN_PATH" ]; then
5407 _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install."
5408 _err "Can not find path:'$DOMAIN_PATH'"
5409 return 1
5410 fi
5411
5412 _savedomainconf "Le_RealCertPath" "$_real_cert"
5413 _savedomainconf "Le_RealCACertPath" "$_real_ca"
5414 _savedomainconf "Le_RealKeyPath" "$_real_key"
5415 _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
5416 _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
5417
5418 _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
5419 }
5420
5421 #domain cert key ca fullchain reloadcmd backup-prefix
5422 _installcert() {
5423 _main_domain="$1"
5424 _real_cert="$2"
5425 _real_key="$3"
5426 _real_ca="$4"
5427 _real_fullchain="$5"
5428 _reload_cmd="$6"
5429 _backup_prefix="$7"
5430
5431 if [ "$_real_cert" = "$NO_VALUE" ]; then
5432 _real_cert=""
5433 fi
5434 if [ "$_real_key" = "$NO_VALUE" ]; then
5435 _real_key=""
5436 fi
5437 if [ "$_real_ca" = "$NO_VALUE" ]; then
5438 _real_ca=""
5439 fi
5440 if [ "$_reload_cmd" = "$NO_VALUE" ]; then
5441 _reload_cmd=""
5442 fi
5443 if [ "$_real_fullchain" = "$NO_VALUE" ]; then
5444 _real_fullchain=""
5445 fi
5446
5447 _backup_path="$DOMAIN_BACKUP_PATH/$_backup_prefix"
5448 mkdir -p "$_backup_path"
5449
5450 if [ "$_real_cert" ]; then
5451 _info "Installing cert to:$_real_cert"
5452 if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
5453 cp "$_real_cert" "$_backup_path/cert.bak"
5454 fi
5455 cat "$CERT_PATH" >"$_real_cert" || return 1
5456 fi
5457
5458 if [ "$_real_ca" ]; then
5459 _info "Installing CA to:$_real_ca"
5460 if [ "$_real_ca" = "$_real_cert" ]; then
5461 echo "" >>"$_real_ca"
5462 cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
5463 else
5464 if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then
5465 cp "$_real_ca" "$_backup_path/ca.bak"
5466 fi
5467 cat "$CA_CERT_PATH" >"$_real_ca" || return 1
5468 fi
5469 fi
5470
5471 if [ "$_real_key" ]; then
5472 _info "Installing key to:$_real_key"
5473 if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
5474 cp "$_real_key" "$_backup_path/key.bak"
5475 fi
5476 if [ -f "$_real_key" ]; then
5477 cat "$CERT_KEY_PATH" >"$_real_key" || return 1
5478 else
5479 cat "$CERT_KEY_PATH" >"$_real_key" || return 1
5480 chmod 600 "$_real_key"
5481 fi
5482 fi
5483
5484 if [ "$_real_fullchain" ]; then
5485 _info "Installing full chain to:$_real_fullchain"
5486 if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
5487 cp "$_real_fullchain" "$_backup_path/fullchain.bak"
5488 fi
5489 cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
5490 fi
5491
5492 if [ "$_reload_cmd" ]; then
5493 _info "Run reload cmd: $_reload_cmd"
5494 if (
5495 export CERT_PATH
5496 export CERT_KEY_PATH
5497 export CA_CERT_PATH
5498 export CERT_FULLCHAIN_PATH
5499 export Le_Domain="$_main_domain"
5500 cd "$DOMAIN_PATH" && eval "$_reload_cmd"
5501 ); then
5502 _info "$(__green "Reload success")"
5503 else
5504 _err "Reload error for :$Le_Domain"
5505 fi
5506 fi
5507
5508 }
5509
5510 __read_password() {
5511 unset _pp
5512 prompt="Enter Password:"
5513 while IFS= read -p "$prompt" -r -s -n 1 char; do
5514 if [ "$char" = $'\0' ]; then
5515 break
5516 fi
5517 prompt='*'
5518 _pp="$_pp$char"
5519 done
5520 echo "$_pp"
5521 }
5522
5523 _install_win_taskscheduler() {
5524 _lesh="$1"
5525 _centry="$2"
5526 _randomminute="$3"
5527 if ! _exists cygpath; then
5528 _err "cygpath not found"
5529 return 1
5530 fi
5531 if ! _exists schtasks; then
5532 _err "schtasks.exe is not found, are you on Windows?"
5533 return 1
5534 fi
5535 _winbash="$(cygpath -w $(which bash))"
5536 _debug _winbash "$_winbash"
5537 if [ -z "$_winbash" ]; then
5538 _err "can not find bash path"
5539 return 1
5540 fi
5541 _myname="$(whoami)"
5542 _debug "_myname" "$_myname"
5543 if [ -z "$_myname" ]; then
5544 _err "can not find my user name"
5545 return 1
5546 fi
5547 _debug "_lesh" "$_lesh"
5548
5549 _info "To install scheduler task in your Windows account, you must input your windows password."
5550 _info "$PROJECT_NAME doesn't save your password."
5551 _info "Please input your Windows password for: $(__green "$_myname")"
5552 _password="$(__read_password)"
5553 #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null
5554 echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null
5555 echo
5556
5557 }
5558
5559 _uninstall_win_taskscheduler() {
5560 if ! _exists schtasks; then
5561 _err "schtasks.exe is not found, are you on Windows?"
5562 return 1
5563 fi
5564 if ! echo SCHTASKS /query /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null; then
5565 _debug "scheduler $_WINDOWS_SCHEDULER_NAME is not found."
5566 else
5567 _info "Removing $_WINDOWS_SCHEDULER_NAME"
5568 echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null
5569 fi
5570 }
5571
5572 #confighome
5573 installcronjob() {
5574 _c_home="$1"
5575 _initpath
5576 _CRONTAB="crontab"
5577 if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then
5578 lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
5579 else
5580 _err "Can not install cronjob, $PROJECT_ENTRY not found."
5581 return 1
5582 fi
5583 if [ "$_c_home" ]; then
5584 _c_entry="--config-home \"$_c_home\" "
5585 fi
5586 _t=$(_time)
5587 random_minute=$(_math $_t % 60)
5588
5589 if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
5590 _CRONTAB="fcrontab"
5591 fi
5592
5593 if ! _exists "$_CRONTAB"; then
5594 if _exists cygpath && _exists schtasks.exe; then
5595 _info "It seems you are on Windows, let's install Windows scheduler task."
5596 if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then
5597 _info "Install Windows scheduler task success."
5598 return 0
5599 else
5600 _err "Install Windows scheduler task failed."
5601 return 1
5602 fi
5603 fi
5604 _err "crontab/fcrontab doesn't exist, so, we can not install cron jobs."
5605 _err "All your certs will not be renewed automatically."
5606 _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday."
5607 return 1
5608 fi
5609 _info "Installing cron job"
5610 if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then
5611 if _exists uname && uname -a | grep SunOS >/dev/null; then
5612 $_CRONTAB -l | {
5613 cat
5614 echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
5615 } | $_CRONTAB --
5616 else
5617 $_CRONTAB -l | {
5618 cat
5619 echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
5620 } | $_CRONTAB -
5621 fi
5622 fi
5623 if [ "$?" != "0" ]; then
5624 _err "Install cron job failed. You need to manually renew your certs."
5625 _err "Or you can add cronjob by yourself:"
5626 _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null"
5627 return 1
5628 fi
5629 }
5630
5631 uninstallcronjob() {
5632 _CRONTAB="crontab"
5633 if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
5634 _CRONTAB="fcrontab"
5635 fi
5636
5637 if ! _exists "$_CRONTAB"; then
5638 if _exists cygpath && _exists schtasks.exe; then
5639 _info "It seems you are on Windows, let's uninstall Windows scheduler task."
5640 if _uninstall_win_taskscheduler; then
5641 _info "Uninstall Windows scheduler task success."
5642 return 0
5643 else
5644 _err "Uninstall Windows scheduler task failed."
5645 return 1
5646 fi
5647 fi
5648 return
5649 fi
5650 _info "Removing cron job"
5651 cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")"
5652 if [ "$cr" ]; then
5653 if _exists uname && uname -a | grep solaris >/dev/null; then
5654 $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB --
5655 else
5656 $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -
5657 fi
5658 LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')"
5659 _info LE_WORKING_DIR "$LE_WORKING_DIR"
5660 if _contains "$cr" "--config-home"; then
5661 LE_CONFIG_HOME="$(echo "$cr" | cut -d ' ' -f 11 | tr -d '"')"
5662 _debug LE_CONFIG_HOME "$LE_CONFIG_HOME"
5663 fi
5664 fi
5665 _initpath
5666
5667 }
5668
5669 #domain isECC revokeReason
5670 revoke() {
5671 Le_Domain="$1"
5672 if [ -z "$Le_Domain" ]; then
5673 _usage "Usage: $PROJECT_ENTRY --revoke -d domain.com [--ecc]"
5674 return 1
5675 fi
5676
5677 _isEcc="$2"
5678 _reason="$3"
5679 if [ -z "$_reason" ]; then
5680 _reason="0"
5681 fi
5682 _initpath "$Le_Domain" "$_isEcc"
5683 if [ ! -f "$DOMAIN_CONF" ]; then
5684 _err "$Le_Domain is not a issued domain, skip."
5685 return 1
5686 fi
5687
5688 if [ ! -f "$CERT_PATH" ]; then
5689 _err "Cert for $Le_Domain $CERT_PATH is not found, skip."
5690 return 1
5691 fi
5692
5693 cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)"
5694
5695 if [ -z "$cert" ]; then
5696 _err "Cert for $Le_Domain is empty found, skip."
5697 return 1
5698 fi
5699
5700 _initAPI
5701
5702 if [ "$ACME_VERSION" = "2" ]; then
5703 data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
5704 else
5705 data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
5706 fi
5707 uri="${ACME_REVOKE_CERT}"
5708
5709 if [ -f "$CERT_KEY_PATH" ]; then
5710 _info "Try domain key first."
5711 if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then
5712 if [ -z "$response" ]; then
5713 _info "Revoke success."
5714 rm -f "$CERT_PATH"
5715 return 0
5716 else
5717 _err "Revoke error by domain key."
5718 _err "$response"
5719 fi
5720 fi
5721 else
5722 _info "Domain key file doesn't exist."
5723 fi
5724
5725 _info "Try account key."
5726
5727 if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then
5728 if [ -z "$response" ]; then
5729 _info "Revoke success."
5730 rm -f "$CERT_PATH"
5731 return 0
5732 else
5733 _err "Revoke error."
5734 _debug "$response"
5735 fi
5736 fi
5737 return 1
5738 }
5739
5740 #domain ecc
5741 remove() {
5742 Le_Domain="$1"
5743 if [ -z "$Le_Domain" ]; then
5744 _usage "Usage: $PROJECT_ENTRY --remove -d domain.com [--ecc]"
5745 return 1
5746 fi
5747
5748 _isEcc="$2"
5749
5750 _initpath "$Le_Domain" "$_isEcc"
5751 _removed_conf="$DOMAIN_CONF.removed"
5752 if [ ! -f "$DOMAIN_CONF" ]; then
5753 if [ -f "$_removed_conf" ]; then
5754 _err "$Le_Domain is already removed, You can remove the folder by yourself: $DOMAIN_PATH"
5755 else
5756 _err "$Le_Domain is not a issued domain, skip."
5757 fi
5758 return 1
5759 fi
5760
5761 if mv "$DOMAIN_CONF" "$_removed_conf"; then
5762 _info "$Le_Domain is removed, the key and cert files are in $(__green $DOMAIN_PATH)"
5763 _info "You can remove them by yourself."
5764 return 0
5765 else
5766 _err "Remove $Le_Domain failed."
5767 return 1
5768 fi
5769 }
5770
5771 #domain vtype
5772 _deactivate() {
5773 _d_domain="$1"
5774 _d_type="$2"
5775 _initpath
5776
5777 if [ "$ACME_VERSION" = "2" ]; then
5778 _identifiers="{\"type\":\"dns\",\"value\":\"$_d_domain\"}"
5779 if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
5780 _err "Can not get domain new order."
5781 return 1
5782 fi
5783 _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
5784 _debug2 _authorizations_seg "$_authorizations_seg"
5785 if [ -z "$_authorizations_seg" ]; then
5786 _err "_authorizations_seg not found."
5787 _clearup
5788 _on_issue_err "$_post_hook"
5789 return 1
5790 fi
5791
5792 authzUri="$_authorizations_seg"
5793 _debug2 "authzUri" "$authzUri"
5794 if ! _send_signed_request "$authzUri"; then
5795 _err "get to authz error."
5796 _err "_authorizations_seg" "$_authorizations_seg"
5797 _err "authzUri" "$authzUri"
5798 _clearup
5799 _on_issue_err "$_post_hook"
5800 return 1
5801 fi
5802
5803 response="$(echo "$response" | _normalizeJson)"
5804 _debug2 response "$response"
5805 _URL_NAME="url"
5806 else
5807 if ! __get_domain_new_authz "$_d_domain"; then
5808 _err "Can not get domain new authz token."
5809 return 1
5810 fi
5811
5812 authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
5813 _debug "authzUri" "$authzUri"
5814 if [ "$code" ] && [ ! "$code" = '201' ]; then
5815 _err "new-authz error: $response"
5816 return 1
5817 fi
5818 _URL_NAME="uri"
5819 fi
5820
5821 entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")"
5822 if [ -z "$entries" ]; then
5823 _info "No valid entries found."
5824 if [ -z "$thumbprint" ]; then
5825 thumbprint="$(__calc_account_thumbprint)"
5826 fi
5827 _debug "Trigger validation."
5828 vtype="$VTYPE_DNS"
5829 entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
5830 _debug entry "$entry"
5831 if [ -z "$entry" ]; then
5832 _err "Error, can not get domain token $d"
5833 return 1
5834 fi
5835 token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
5836 _debug token "$token"
5837
5838 uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
5839 _debug uri "$uri"
5840
5841 keyauthorization="$token.$thumbprint"
5842 _debug keyauthorization "$keyauthorization"
5843 __trigger_validation "$uri" "$keyauthorization"
5844
5845 fi
5846
5847 _d_i=0
5848 _d_max_retry=$(echo "$entries" | wc -l)
5849 while [ "$_d_i" -lt "$_d_max_retry" ]; do
5850 _info "Deactivate: $_d_domain"
5851 _d_i="$(_math $_d_i + 1)"
5852 entry="$(echo "$entries" | sed -n "${_d_i}p")"
5853 _debug entry "$entry"
5854
5855 if [ -z "$entry" ]; then
5856 _info "No more valid entry found."
5857 break
5858 fi
5859
5860 _vtype="$(echo "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')"
5861 _debug _vtype "$_vtype"
5862 _info "Found $_vtype"
5863
5864 uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
5865 _debug uri "$uri"
5866
5867 if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then
5868 _info "Skip $_vtype"
5869 continue
5870 fi
5871
5872 _info "Deactivate: $_vtype"
5873
5874 if [ "$ACME_VERSION" = "2" ]; then
5875 _djson="{\"status\":\"deactivated\"}"
5876 else
5877 _djson="{\"resource\": \"authz\", \"status\":\"deactivated\"}"
5878 fi
5879
5880 if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then
5881 _info "Deactivate: $_vtype success."
5882 else
5883 _err "Can not deactivate $_vtype."
5884 break
5885 fi
5886
5887 done
5888 _debug "$_d_i"
5889 if [ "$_d_i" -eq "$_d_max_retry" ]; then
5890 _info "Deactivated success!"
5891 else
5892 _err "Deactivate failed."
5893 fi
5894
5895 }
5896
5897 deactivate() {
5898 _d_domain_list="$1"
5899 _d_type="$2"
5900 _initpath
5901 _initAPI
5902 _debug _d_domain_list "$_d_domain_list"
5903 if [ -z "$(echo $_d_domain_list | cut -d , -f 1)" ]; then
5904 _usage "Usage: $PROJECT_ENTRY --deactivate -d domain.com [-d domain.com]"
5905 return 1
5906 fi
5907 for _d_dm in $(echo "$_d_domain_list" | tr ',' ' '); do
5908 if [ -z "$_d_dm" ] || [ "$_d_dm" = "$NO_VALUE" ]; then
5909 continue
5910 fi
5911 if ! _deactivate "$_d_dm" "$_d_type"; then
5912 return 1
5913 fi
5914 done
5915 }
5916
5917 # Detect profile file if not specified as environment variable
5918 _detect_profile() {
5919 if [ -n "$PROFILE" -a -f "$PROFILE" ]; then
5920 echo "$PROFILE"
5921 return
5922 fi
5923
5924 DETECTED_PROFILE=''
5925 SHELLTYPE="$(basename "/$SHELL")"
5926
5927 if [ "$SHELLTYPE" = "bash" ]; then
5928 if [ -f "$HOME/.bashrc" ]; then
5929 DETECTED_PROFILE="$HOME/.bashrc"
5930 elif [ -f "$HOME/.bash_profile" ]; then
5931 DETECTED_PROFILE="$HOME/.bash_profile"
5932 fi
5933 elif [ "$SHELLTYPE" = "zsh" ]; then
5934 DETECTED_PROFILE="$HOME/.zshrc"
5935 fi
5936
5937 if [ -z "$DETECTED_PROFILE" ]; then
5938 if [ -f "$HOME/.profile" ]; then
5939 DETECTED_PROFILE="$HOME/.profile"
5940 elif [ -f "$HOME/.bashrc" ]; then
5941 DETECTED_PROFILE="$HOME/.bashrc"
5942 elif [ -f "$HOME/.bash_profile" ]; then
5943 DETECTED_PROFILE="$HOME/.bash_profile"
5944 elif [ -f "$HOME/.zshrc" ]; then
5945 DETECTED_PROFILE="$HOME/.zshrc"
5946 fi
5947 fi
5948
5949 echo "$DETECTED_PROFILE"
5950 }
5951
5952 _initconf() {
5953 _initpath
5954 if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
5955 echo "
5956
5957 #LOG_FILE=\"$DEFAULT_LOG_FILE\"
5958 #LOG_LEVEL=1
5959
5960 #AUTO_UPGRADE=\"1\"
5961
5962 #NO_TIMESTAMP=1
5963
5964 " >"$ACCOUNT_CONF_PATH"
5965 fi
5966 }
5967
5968 # nocron
5969 _precheck() {
5970 _nocron="$1"
5971
5972 if ! _exists "curl" && ! _exists "wget"; then
5973 _err "Please install curl or wget first, we need to access http resources."
5974 return 1
5975 fi
5976
5977 if [ -z "$_nocron" ]; then
5978 if ! _exists "crontab" && ! _exists "fcrontab"; then
5979 if _exists cygpath && _exists schtasks.exe; then
5980 _info "It seems you are on Windows, we will install Windows scheduler task."
5981 else
5982 _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
5983 _err "We need to set cron job to renew the certs automatically."
5984 _err "Otherwise, your certs will not be able to be renewed automatically."
5985 if [ -z "$FORCE" ]; then
5986 _err "Please add '--force' and try install again to go without crontab."
5987 _err "./$PROJECT_ENTRY --install --force"
5988 return 1
5989 fi
5990 fi
5991 fi
5992 fi
5993
5994 if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
5995 _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
5996 _err "We need openssl to generate keys."
5997 return 1
5998 fi
5999
6000 if ! _exists "socat"; then
6001 _err "It is recommended to install socat first."
6002 _err "We use socat for standalone server if you use standalone mode."
6003 _err "If you don't use standalone mode, just ignore this warning."
6004 fi
6005
6006 return 0
6007 }
6008
6009 _setShebang() {
6010 _file="$1"
6011 _shebang="$2"
6012 if [ -z "$_shebang" ]; then
6013 _usage "Usage: file shebang"
6014 return 1
6015 fi
6016 cp "$_file" "$_file.tmp"
6017 echo "$_shebang" >"$_file"
6018 sed -n 2,99999p "$_file.tmp" >>"$_file"
6019 rm -f "$_file.tmp"
6020 }
6021
6022 #confighome
6023 _installalias() {
6024 _c_home="$1"
6025 _initpath
6026
6027 _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env"
6028 if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then
6029 echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile"
6030 echo "$(cat "$_envfile")" | sed "s|^alias le.*$||" >"$_envfile"
6031 echo "$(cat "$_envfile")" | sed "s|^alias le.sh.*$||" >"$_envfile"
6032 fi
6033
6034 if [ "$_c_home" ]; then
6035 _c_entry=" --config-home '$_c_home'"
6036 fi
6037
6038 _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
6039 if [ "$_c_home" ]; then
6040 _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
6041 else
6042 _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
6043 fi
6044 _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6045
6046 _profile="$(_detect_profile)"
6047 if [ "$_profile" ]; then
6048 _debug "Found profile: $_profile"
6049 _info "Installing alias to '$_profile'"
6050 _setopt "$_profile" ". \"$_envfile\""
6051 _info "OK, Close and reopen your terminal to start using $PROJECT_NAME"
6052 else
6053 _info "No profile is found, you will need to go into $LE_WORKING_DIR to use $PROJECT_NAME"
6054 fi
6055
6056 #for csh
6057 _cshfile="$LE_WORKING_DIR/$PROJECT_ENTRY.csh"
6058 _csh_profile="$HOME/.cshrc"
6059 if [ -f "$_csh_profile" ]; then
6060 _info "Installing alias to '$_csh_profile'"
6061 _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
6062 if [ "$_c_home" ]; then
6063 _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
6064 else
6065 _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
6066 fi
6067 _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6068 _setopt "$_csh_profile" "source \"$_cshfile\""
6069 fi
6070
6071 #for tcsh
6072 _tcsh_profile="$HOME/.tcshrc"
6073 if [ -f "$_tcsh_profile" ]; then
6074 _info "Installing alias to '$_tcsh_profile'"
6075 _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
6076 if [ "$_c_home" ]; then
6077 _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
6078 fi
6079 _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6080 _setopt "$_tcsh_profile" "source \"$_cshfile\""
6081 fi
6082
6083 }
6084
6085 # nocron confighome noprofile
6086 install() {
6087
6088 if [ -z "$LE_WORKING_DIR" ]; then
6089 LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
6090 fi
6091
6092 _nocron="$1"
6093 _c_home="$2"
6094 _noprofile="$3"
6095 if ! _initpath; then
6096 _err "Install failed."
6097 return 1
6098 fi
6099 if [ "$_nocron" ]; then
6100 _debug "Skip install cron job"
6101 fi
6102
6103 if [ "$_ACME_IN_CRON" != "1" ]; then
6104 if ! _precheck "$_nocron"; then
6105 _err "Pre-check failed, can not install."
6106 return 1
6107 fi
6108 fi
6109
6110 if [ -z "$_c_home" ] && [ "$LE_CONFIG_HOME" != "$LE_WORKING_DIR" ]; then
6111 _info "Using config home: $LE_CONFIG_HOME"
6112 _c_home="$LE_CONFIG_HOME"
6113 fi
6114
6115 #convert from le
6116 if [ -d "$HOME/.le" ]; then
6117 for envfile in "le.env" "le.sh.env"; do
6118 if [ -f "$HOME/.le/$envfile" ]; then
6119 if grep "le.sh" "$HOME/.le/$envfile" >/dev/null; then
6120 _upgrading="1"
6121 _info "You are upgrading from le.sh"
6122 _info "Renaming \"$HOME/.le\" to $LE_WORKING_DIR"
6123 mv "$HOME/.le" "$LE_WORKING_DIR"
6124 mv "$LE_WORKING_DIR/$envfile" "$LE_WORKING_DIR/$PROJECT_ENTRY.env"
6125 break
6126 fi
6127 fi
6128 done
6129 fi
6130
6131 _info "Installing to $LE_WORKING_DIR"
6132
6133 if [ ! -d "$LE_WORKING_DIR" ]; then
6134 if ! mkdir -p "$LE_WORKING_DIR"; then
6135 _err "Can not create working dir: $LE_WORKING_DIR"
6136 return 1
6137 fi
6138
6139 chmod 700 "$LE_WORKING_DIR"
6140 fi
6141
6142 if [ ! -d "$LE_CONFIG_HOME" ]; then
6143 if ! mkdir -p "$LE_CONFIG_HOME"; then
6144 _err "Can not create config dir: $LE_CONFIG_HOME"
6145 return 1
6146 fi
6147
6148 chmod 700 "$LE_CONFIG_HOME"
6149 fi
6150
6151 cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
6152
6153 if [ "$?" != "0" ]; then
6154 _err "Install failed, can not copy $PROJECT_ENTRY"
6155 return 1
6156 fi
6157
6158 _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
6159
6160 if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
6161 _installalias "$_c_home"
6162 fi
6163
6164 for subf in $_SUB_FOLDERS; do
6165 if [ -d "$subf" ]; then
6166 mkdir -p "$LE_WORKING_DIR/$subf"
6167 cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/
6168 fi
6169 done
6170
6171 if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
6172 _initconf
6173 fi
6174
6175 if [ "$_DEFAULT_ACCOUNT_CONF_PATH" != "$ACCOUNT_CONF_PATH" ]; then
6176 _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
6177 fi
6178
6179 if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then
6180 _saveaccountconf "CERT_HOME" "$CERT_HOME"
6181 fi
6182
6183 if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then
6184 _saveaccountconf "ACCOUNT_KEY_PATH" "$ACCOUNT_KEY_PATH"
6185 fi
6186
6187 if [ -z "$_nocron" ]; then
6188 installcronjob "$_c_home"
6189 fi
6190
6191 if [ -z "$NO_DETECT_SH" ]; then
6192 #Modify shebang
6193 if _exists bash; then
6194 _bash_path="$(bash -c "command -v bash 2>/dev/null")"
6195 if [ -z "$_bash_path" ]; then
6196 _bash_path="$(bash -c 'echo $SHELL')"
6197 fi
6198 fi
6199 if [ "$_bash_path" ]; then
6200 _info "Good, bash is found, so change the shebang to use bash as preferred."
6201 _shebang='#!'"$_bash_path"
6202 _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang"
6203 for subf in $_SUB_FOLDERS; do
6204 if [ -d "$LE_WORKING_DIR/$subf" ]; then
6205 for _apifile in "$LE_WORKING_DIR/$subf/"*.sh; do
6206 _setShebang "$_apifile" "$_shebang"
6207 done
6208 fi
6209 done
6210 fi
6211 fi
6212
6213 _info OK
6214 }
6215
6216 # nocron
6217 uninstall() {
6218 _nocron="$1"
6219 if [ -z "$_nocron" ]; then
6220 uninstallcronjob
6221 fi
6222 _initpath
6223
6224 _uninstallalias
6225
6226 rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY"
6227 _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\", you can remove them by yourself."
6228
6229 }
6230
6231 _uninstallalias() {
6232 _initpath
6233
6234 _profile="$(_detect_profile)"
6235 if [ "$_profile" ]; then
6236 _info "Uninstalling alias from: '$_profile'"
6237 text="$(cat "$_profile")"
6238 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.env\"$||" >"$_profile"
6239 fi
6240
6241 _csh_profile="$HOME/.cshrc"
6242 if [ -f "$_csh_profile" ]; then
6243 _info "Uninstalling alias from: '$_csh_profile'"
6244 text="$(cat "$_csh_profile")"
6245 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_csh_profile"
6246 fi
6247
6248 _tcsh_profile="$HOME/.tcshrc"
6249 if [ -f "$_tcsh_profile" ]; then
6250 _info "Uninstalling alias from: '$_csh_profile'"
6251 text="$(cat "$_tcsh_profile")"
6252 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_tcsh_profile"
6253 fi
6254
6255 }
6256
6257 cron() {
6258 export _ACME_IN_CRON=1
6259 _initpath
6260 _info "$(__green "===Starting cron===")"
6261 if [ "$AUTO_UPGRADE" = "1" ]; then
6262 export LE_WORKING_DIR
6263 (
6264 if ! upgrade; then
6265 _err "Cron:Upgrade failed!"
6266 return 1
6267 fi
6268 )
6269 . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null
6270
6271 if [ -t 1 ]; then
6272 __INTERACTIVE="1"
6273 fi
6274
6275 _info "Auto upgraded to: $VER"
6276 fi
6277 renewAll
6278 _ret="$?"
6279 _ACME_IN_CRON=""
6280 _info "$(__green "===End cron===")"
6281 exit $_ret
6282 }
6283
6284 version() {
6285 echo "$PROJECT"
6286 echo "v$VER"
6287 }
6288
6289 # subject content hooks code
6290 _send_notify() {
6291 _nsubject="$1"
6292 _ncontent="$2"
6293 _nhooks="$3"
6294 _nerror="$4"
6295
6296 if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then
6297 _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, disabled, just return."
6298 return 0
6299 fi
6300
6301 if [ -z "$_nhooks" ]; then
6302 _debug "The NOTIFY_HOOK is empty, just return."
6303 return 0
6304 fi
6305
6306 _send_err=0
6307 for _n_hook in $(echo "$_nhooks" | tr ',' " "); do
6308 _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")"
6309 _info "Sending via: $_n_hook"
6310 _debug "Found $_n_hook_file for $_n_hook"
6311 if [ -z "$_n_hook_file" ]; then
6312 _err "Can not find the hook file for $_n_hook"
6313 continue
6314 fi
6315 if ! (
6316 if ! . "$_n_hook_file"; then
6317 _err "Load file $_n_hook_file error. Please check your api file and try again."
6318 return 1
6319 fi
6320
6321 d_command="${_n_hook}_send"
6322 if ! _exists "$d_command"; then
6323 _err "It seems that your api file is not correct, it must have a function named: $d_command"
6324 return 1
6325 fi
6326
6327 if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then
6328 _err "Error send message by $d_command"
6329 return 1
6330 fi
6331
6332 return 0
6333 ); then
6334 _err "Set $_n_hook_file error."
6335 _send_err=1
6336 else
6337 _info "$_n_hook $(__green Success)"
6338 fi
6339 done
6340 return $_send_err
6341
6342 }
6343
6344 # hook
6345 _set_notify_hook() {
6346 _nhooks="$1"
6347
6348 _test_subject="Hello, this is a notification from $PROJECT_NAME"
6349 _test_content="If you receive this message, your notification works."
6350
6351 _send_notify "$_test_subject" "$_test_content" "$_nhooks" 0
6352
6353 }
6354
6355 #[hook] [level] [mode]
6356 setnotify() {
6357 _nhook="$1"
6358 _nlevel="$2"
6359 _nmode="$3"
6360
6361 _initpath
6362
6363 if [ -z "$_nhook$_nlevel$_nmode" ]; then
6364 _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook mailgun] [--notify-level $NOTIFY_LEVEL_DEFAULT] [--notify-mode $NOTIFY_MODE_DEFAULT]"
6365 _usage "$_NOTIFY_WIKI"
6366 return 1
6367 fi
6368
6369 if [ "$_nlevel" ]; then
6370 _info "Set notify level to: $_nlevel"
6371 export "NOTIFY_LEVEL=$_nlevel"
6372 _saveaccountconf "NOTIFY_LEVEL" "$NOTIFY_LEVEL"
6373 fi
6374
6375 if [ "$_nmode" ]; then
6376 _info "Set notify mode to: $_nmode"
6377 export "NOTIFY_MODE=$_nmode"
6378 _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE"
6379 fi
6380
6381 if [ "$_nhook" ]; then
6382 _info "Set notify hook to: $_nhook"
6383 if [ "$_nhook" = "$NO_VALUE" ]; then
6384 _info "Clear notify hook"
6385 _clearaccountconf "NOTIFY_HOOK"
6386 else
6387 if _set_notify_hook "$_nhook"; then
6388 export NOTIFY_HOOK="$_nhook"
6389 _saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK"
6390 return 0
6391 else
6392 _err "Can not set notify hook to: $_nhook"
6393 return 1
6394 fi
6395 fi
6396 fi
6397
6398 }
6399
6400 showhelp() {
6401 _initpath
6402 version
6403 echo "Usage: $PROJECT_ENTRY command ...[parameters]....
6404 Commands:
6405 --help, -h Show this help message.
6406 --version, -v Show version info.
6407 --install Install $PROJECT_NAME to your system.
6408 --uninstall Uninstall $PROJECT_NAME, and uninstall the cron job.
6409 --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT.
6410 --issue Issue a cert.
6411 --signcsr Issue a cert from an existing csr.
6412 --deploy Deploy the cert to your server.
6413 --install-cert Install the issued cert to apache/nginx or any other server.
6414 --renew, -r Renew a cert.
6415 --renew-all Renew all the certs.
6416 --revoke Revoke a cert.
6417 --remove Remove the cert from list of certs known to $PROJECT_NAME.
6418 --list List all the certs.
6419 --showcsr Show the content of a csr.
6420 --install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
6421 --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
6422 --cron Run cron job to renew all the certs.
6423 --toPkcs Export the certificate and key to a pfx file.
6424 --toPkcs8 Convert to pkcs8 format.
6425 --update-account Update account info.
6426 --register-account Register account key.
6427 --deactivate-account Deactivate the account.
6428 --create-account-key Create an account private key, professional use.
6429 --create-domain-key Create an domain private key, professional use.
6430 --createCSR, -ccsr Create CSR , professional use.
6431 --deactivate Deactivate the domain authz, professional use.
6432 --set-notify Set the cron notification hook, level or mode.
6433 --set-default-ca Used with '--server' , to set the default CA to use to use.
6434
6435
6436 Parameters:
6437 --domain, -d domain.tld Specifies a domain, used to issue, renew or revoke etc.
6438 --challenge-alias domain.tld The challenge domain alias for DNS alias mode.
6439 See: $_DNS_ALIAS_WIKI
6440
6441 --domain-alias domain.tld The domain alias for DNS alias mode.
6442 See: $_DNS_ALIAS_WIKI
6443
6444 --preferred-chain CHAIN If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
6445 If no match, the default offered chain will be used. (default: empty)
6446 See: $_PREFERRED_CHAIN_WIKI
6447
6448 --force, -f Used to force to install or force to renew a cert immediately.
6449 --staging, --test Use staging server, just for test.
6450 --debug Output debug info.
6451 --output-insecure Output all the sensitive messages.
6452 By default all the credentials/sensitive messages are hidden from the output/debug/log for security.
6453
6454 --webroot, -w /path/to/webroot Specifies the web root folder for web root mode.
6455 --standalone Use standalone mode.
6456 --alpn Use standalone alpn mode.
6457 --stateless Use stateless mode.
6458 See: $_STATELESS_WIKI
6459
6460 --apache Use apache mode.
6461 --dns [dns_hook] Use dns mode or dns api.
6462 See: $_DNS_API_WIKI
6463
6464 --dnssleep 300 The time in seconds to wait for all the txt records to propagate in dns api mode.
6465 It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
6466
6467 --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
6468 --accountkeylength, -ak [2048] Specifies the account key length: 2048, 3072, 4096
6469 --log [/path/to/logfile] Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
6470 --log-level 1|2 Specifies the log level, default is 1.
6471 --syslog [0|3|6|7] Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
6472
6473 --eab-kid EAB_KID Key Identifier for External Account Binding.
6474 --eab-hmac-key EAB_HMAC_KEY HMAC key for External Account Binding.
6475
6476
6477 These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert:
6478
6479 --cert-file After issue/renew, the cert will be copied to this path.
6480 --key-file After issue/renew, the key will be copied to this path.
6481 --ca-file After issue/renew, the intermediate cert will be copied to this path.
6482 --fullchain-file After issue/renew, the fullchain cert will be copied to this path.
6483
6484 --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
6485
6486 --server SERVER ACME Directory Resource URI. (default: $DEFAULT_CA)
6487 See: $_SERVER_WIKI
6488
6489 --accountconf Specifies a customized account config file.
6490 --home Specifies the home dir for $PROJECT_NAME.
6491 --cert-home Specifies the home dir to save all the certs, only valid for '--install' command.
6492 --config-home Specifies the home dir to save all the configurations.
6493 --useragent Specifies the user agent string. it will be saved for future use too.
6494 --accountemail, -m Specifies the account email, only valid for the '--install' and '--update-account' command.
6495 --accountkey Specifies the account key path, only valid for the '--install' command.
6496 --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
6497 --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
6498 --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
6499 --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
6500 --listraw Only used for '--list' command, list the certs in raw format.
6501 --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal.
6502 --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
6503 --ca-bundle Specifies the path to the CA certificate bundle to verify api server's certificate.
6504 --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl.
6505 --nocron Only valid for '--install' command, which means: do not install the default cron job.
6506 In this case, the certs will not be renewed automatically.
6507
6508 --noprofile Only valid for '--install' command, which means: do not install aliases to user profile.
6509 --no-color Do not output color text.
6510 --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
6511 --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
6512 --csr Specifies the input csr.
6513 --pre-hook Command to be run before obtaining any certificates.
6514 --post-hook Command to be run after attempting to obtain/renew certificates. No matter the obtain/renew is success or failed.
6515 --renew-hook Command to be run once for each successfully renewed certificate.
6516 --deploy-hook The hook file to deploy cert
6517 --ocsp-must-staple, --ocsp Generate ocsp must Staple extension.
6518 --always-force-new-domain-key Generate new domain key when renewal. Otherwise, the domain key is not changed by default.
6519 --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future.
6520 --listen-v4 Force standalone/tls server to listen at ipv4.
6521 --listen-v6 Force standalone/tls server to listen at ipv6.
6522 --openssl-bin Specifies a custom openssl bin location.
6523 --use-wget Force to use wget, if you have both curl and wget installed.
6524 --yes-I-know-dns-manual-mode-enough-go-ahead-please Force to use dns manual mode.
6525 See: $_DNS_MANUAL_WIKI
6526
6527 --branch, -b Only valid for '--upgrade' command, specifies the branch name to upgrade to.
6528
6529 --notify-level 0|1|2|3 Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT.
6530 0: disabled, no notification will be sent.
6531 1: send notifications only when there is an error.
6532 2: send notifications when a cert is successfully renewed, or there is an error.
6533 3: send notifications when a cert is skipped, renewed, or error.
6534
6535 --notify-mode 0|1 Set notification mode. Default value is $NOTIFY_MODE_DEFAULT.
6536 0: Bulk mode. Send all the domain's notifications in one message(mail).
6537 1: Cert mode. Send a message for every single cert.
6538
6539 --notify-hook [hookname] Set the notify hook
6540 --revoke-reason [0-10] The reason for '--revoke' command.
6541 See: $_REVOKE_WIKI
6542
6543
6544 "
6545 }
6546
6547 # nocron noprofile
6548 _installOnline() {
6549 _info "Installing from online archive."
6550 _nocron="$1"
6551 _noprofile="$2"
6552 if [ ! "$BRANCH" ]; then
6553 BRANCH="master"
6554 fi
6555
6556 target="$PROJECT/archive/$BRANCH.tar.gz"
6557 _info "Downloading $target"
6558 localname="$BRANCH.tar.gz"
6559 if ! _get "$target" >$localname; then
6560 _err "Download error."
6561 return 1
6562 fi
6563 (
6564 _info "Extracting $localname"
6565 if ! (tar xzf $localname || gtar xzf $localname); then
6566 _err "Extraction error."
6567 exit 1
6568 fi
6569
6570 cd "$PROJECT_NAME-$BRANCH"
6571 chmod +x $PROJECT_ENTRY
6572 if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then
6573 _info "Install success!"
6574 _initpath
6575 _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
6576 fi
6577
6578 cd ..
6579
6580 rm -rf "$PROJECT_NAME-$BRANCH"
6581 rm -f "$localname"
6582 )
6583 }
6584
6585 _getRepoHash() {
6586 _hash_path=$1
6587 shift
6588 _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path"
6589 _get $_hash_url | tr -d "\r\n" | tr '{},' '\n' | grep '"sha":' | cut -d '"' -f 4
6590 }
6591
6592 _getUpgradeHash() {
6593 _b="$BRANCH"
6594 if [ -z "$_b" ]; then
6595 _b="master"
6596 fi
6597 _hash=$(_getRepoHash "heads/$_b")
6598 if [ -z "$_hash" ]; then _hash=$(_getRepoHash "tags/$_b"); fi
6599 echo $_hash
6600 }
6601
6602 upgrade() {
6603 if (
6604 _initpath
6605 [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already uptodate!" && exit 0
6606 export LE_WORKING_DIR
6607 cd "$LE_WORKING_DIR"
6608 _installOnline "nocron" "noprofile"
6609 ); then
6610 _info "Upgrade success!"
6611 exit 0
6612 else
6613 _err "Upgrade failed!"
6614 exit 1
6615 fi
6616 }
6617
6618 _processAccountConf() {
6619 if [ "$_useragent" ]; then
6620 _saveaccountconf "USER_AGENT" "$_useragent"
6621 elif [ "$USER_AGENT" ] && [ "$USER_AGENT" != "$DEFAULT_USER_AGENT" ]; then
6622 _saveaccountconf "USER_AGENT" "$USER_AGENT"
6623 fi
6624
6625 if [ "$_openssl_bin" ]; then
6626 _saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin"
6627 elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then
6628 _saveaccountconf "ACME_OPENSSL_BIN" "$ACME_OPENSSL_BIN"
6629 fi
6630
6631 if [ "$_auto_upgrade" ]; then
6632 _saveaccountconf "AUTO_UPGRADE" "$_auto_upgrade"
6633 elif [ "$AUTO_UPGRADE" ]; then
6634 _saveaccountconf "AUTO_UPGRADE" "$AUTO_UPGRADE"
6635 fi
6636
6637 if [ "$_use_wget" ]; then
6638 _saveaccountconf "ACME_USE_WGET" "$_use_wget"
6639 elif [ "$ACME_USE_WGET" ]; then
6640 _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET"
6641 fi
6642
6643 }
6644
6645 _checkSudo() {
6646 if [ "$SUDO_GID" ] && [ "$SUDO_COMMAND" ] && [ "$SUDO_USER" ] && [ "$SUDO_UID" ]; then
6647 if [ "$SUDO_USER" = "root" ] && [ "$SUDO_UID" = "0" ]; then
6648 #it's root using sudo, no matter it's using sudo or not, just fine
6649 return 0
6650 fi
6651 if [ -n "$SUDO_COMMAND" ]; then
6652 #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`
6653 _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1
6654 return $?
6655 fi
6656 #otherwise
6657 return 1
6658 fi
6659 return 0
6660 }
6661
6662 #server
6663 _selectServer() {
6664 _server="$1"
6665 _server_lower="$(echo "$_server" | _lower_case)"
6666 _sindex=0
6667 for snames in $CA_NAMES; do
6668 snames="$(echo "$snames" | _lower_case)"
6669 _sindex="$(_math $_sindex + 1)"
6670 _debug2 "_selectServer try snames" "$snames"
6671 for sname in $(echo "$snames" | tr ',' ' '); do
6672 if [ "$_server_lower" = "$sname" ]; then
6673 _debug2 "_selectServer match $sname"
6674 _serverdir="$(_getfield "$CA_SERVERS" $_sindex)"
6675 _debug "Selected server: $_serverdir"
6676 ACME_DIRECTORY="$_serverdir"
6677 export ACME_DIRECTORY
6678 return
6679 fi
6680 done
6681 done
6682 ACME_DIRECTORY="$_server"
6683 export ACME_DIRECTORY
6684 }
6685
6686 #url
6687 _getCAShortName() {
6688 caurl="$1"
6689 if [ -z "$caurl" ]; then
6690 caurl="$DEFAULT_CA"
6691 fi
6692 caurl_lower="$(echo $caurl | _lower_case)"
6693 _sindex=0
6694 for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do
6695 _sindex="$(_math $_sindex + 1)"
6696 if [ "$caurl_lower" = "$surl" ]; then
6697 _nindex=0
6698 for snames in $CA_NAMES; do
6699 _nindex="$(_math $_nindex + 1)"
6700 if [ $_nindex -ge $_sindex ]; then
6701 _getfield "$snames" 1
6702 return
6703 fi
6704 done
6705 fi
6706 done
6707 echo "$caurl"
6708 }
6709
6710 #set default ca to $ACME_DIRECTORY
6711 setdefaultca() {
6712 if [ -z "$ACME_DIRECTORY" ]; then
6713 _err "Please give a --server parameter."
6714 return 1
6715 fi
6716 _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY"
6717 _info "Changed default CA to: $(__green "$ACME_DIRECTORY")"
6718 }
6719
6720 _process() {
6721 _CMD=""
6722 _domain=""
6723 _altdomains="$NO_VALUE"
6724 _webroot=""
6725 _challenge_alias=""
6726 _keylength=""
6727 _accountkeylength=""
6728 _cert_file=""
6729 _key_file=""
6730 _ca_file=""
6731 _fullchain_file=""
6732 _reloadcmd=""
6733 _password=""
6734 _accountconf=""
6735 _useragent=""
6736 _accountemail=""
6737 _accountkey=""
6738 _certhome=""
6739 _confighome=""
6740 _httpport=""
6741 _tlsport=""
6742 _dnssleep=""
6743 _listraw=""
6744 _stopRenewOnError=""
6745 #_insecure=""
6746 _ca_bundle=""
6747 _ca_path=""
6748 _nocron=""
6749 _noprofile=""
6750 _ecc=""
6751 _csr=""
6752 _pre_hook=""
6753 _post_hook=""
6754 _renew_hook=""
6755 _deploy_hook=""
6756 _logfile=""
6757 _log=""
6758 _local_address=""
6759 _log_level=""
6760 _auto_upgrade=""
6761 _listen_v4=""
6762 _listen_v6=""
6763 _openssl_bin=""
6764 _syslog=""
6765 _use_wget=""
6766 _server=""
6767 _notify_hook=""
6768 _notify_level=""
6769 _notify_mode=""
6770 _revoke_reason=""
6771 _eab_kid=""
6772 _eab_hmac_key=""
6773 _preferred_chain=""
6774 while [ ${#} -gt 0 ]; do
6775 case "${1}" in
6776
6777 --help | -h)
6778 showhelp
6779 return
6780 ;;
6781 --version | -v)
6782 version
6783 return
6784 ;;
6785 --install)
6786 _CMD="install"
6787 ;;
6788 --uninstall)
6789 _CMD="uninstall"
6790 ;;
6791 --upgrade)
6792 _CMD="upgrade"
6793 ;;
6794 --issue)
6795 _CMD="issue"
6796 ;;
6797 --deploy)
6798 _CMD="deploy"
6799 ;;
6800 --signcsr)
6801 _CMD="signcsr"
6802 ;;
6803 --showcsr)
6804 _CMD="showcsr"
6805 ;;
6806 --installcert | -i | --install-cert)
6807 _CMD="installcert"
6808 ;;
6809 --renew | -r)
6810 _CMD="renew"
6811 ;;
6812 --renewAll | --renewall | --renew-all)
6813 _CMD="renewAll"
6814 ;;
6815 --revoke)
6816 _CMD="revoke"
6817 ;;
6818 --remove)
6819 _CMD="remove"
6820 ;;
6821 --list)
6822 _CMD="list"
6823 ;;
6824 --installcronjob | --install-cronjob)
6825 _CMD="installcronjob"
6826 ;;
6827 --uninstallcronjob | --uninstall-cronjob)
6828 _CMD="uninstallcronjob"
6829 ;;
6830 --cron)
6831 _CMD="cron"
6832 ;;
6833 --toPkcs)
6834 _CMD="toPkcs"
6835 ;;
6836 --toPkcs8)
6837 _CMD="toPkcs8"
6838 ;;
6839 --createAccountKey | --createaccountkey | -cak | --create-account-key)
6840 _CMD="createAccountKey"
6841 ;;
6842 --createDomainKey | --createdomainkey | -cdk | --create-domain-key)
6843 _CMD="createDomainKey"
6844 ;;
6845 --createCSR | --createcsr | -ccr)
6846 _CMD="createCSR"
6847 ;;
6848 --deactivate)
6849 _CMD="deactivate"
6850 ;;
6851 --updateaccount | --update-account)
6852 _CMD="updateaccount"
6853 ;;
6854 --registeraccount | --register-account)
6855 _CMD="registeraccount"
6856 ;;
6857 --deactivate-account)
6858 _CMD="deactivateaccount"
6859 ;;
6860 --set-notify)
6861 _CMD="setnotify"
6862 ;;
6863 --set-default-ca)
6864 _CMD="setdefaultca"
6865 ;;
6866 --domain | -d)
6867 _dvalue="$2"
6868
6869 if [ "$_dvalue" ]; then
6870 if _startswith "$_dvalue" "-"; then
6871 _err "'$_dvalue' is not a valid domain for parameter '$1'"
6872 return 1
6873 fi
6874 if _is_idn "$_dvalue" && ! _exists idn; then
6875 _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first."
6876 return 1
6877 fi
6878
6879 if _startswith "$_dvalue" "*."; then
6880 _debug "Wildcard domain"
6881 export ACME_VERSION=2
6882 fi
6883 if [ -z "$_domain" ]; then
6884 _domain="$_dvalue"
6885 else
6886 if [ "$_altdomains" = "$NO_VALUE" ]; then
6887 _altdomains="$_dvalue"
6888 else
6889 _altdomains="$_altdomains,$_dvalue"
6890 fi
6891 fi
6892 fi
6893
6894 shift
6895 ;;
6896
6897 --force | -f)
6898 FORCE="1"
6899 ;;
6900 --staging | --test)
6901 STAGE="1"
6902 ;;
6903 --server)
6904 _server="$2"
6905 _selectServer "$_server"
6906 shift
6907 ;;
6908 --debug)
6909 if [ -z "$2" ] || _startswith "$2" "-"; then
6910 DEBUG="$DEBUG_LEVEL_DEFAULT"
6911 else
6912 DEBUG="$2"
6913 shift
6914 fi
6915 ;;
6916 --output-insecure)
6917 export OUTPUT_INSECURE=1
6918 ;;
6919 --webroot | -w)
6920 wvalue="$2"
6921 if [ -z "$_webroot" ]; then
6922 _webroot="$wvalue"
6923 else
6924 _webroot="$_webroot,$wvalue"
6925 fi
6926 shift
6927 ;;
6928 --challenge-alias)
6929 cvalue="$2"
6930 _challenge_alias="$_challenge_alias$cvalue,"
6931 shift
6932 ;;
6933 --domain-alias)
6934 cvalue="$DNS_ALIAS_PREFIX$2"
6935 _challenge_alias="$_challenge_alias$cvalue,"
6936 shift
6937 ;;
6938 --standalone)
6939 wvalue="$NO_VALUE"
6940 if [ -z "$_webroot" ]; then
6941 _webroot="$wvalue"
6942 else
6943 _webroot="$_webroot,$wvalue"
6944 fi
6945 ;;
6946 --alpn)
6947 wvalue="$W_ALPN"
6948 if [ -z "$_webroot" ]; then
6949 _webroot="$wvalue"
6950 else
6951 _webroot="$_webroot,$wvalue"
6952 fi
6953 ;;
6954 --stateless)
6955 wvalue="$MODE_STATELESS"
6956 if [ -z "$_webroot" ]; then
6957 _webroot="$wvalue"
6958 else
6959 _webroot="$_webroot,$wvalue"
6960 fi
6961 ;;
6962 --local-address)
6963 lvalue="$2"
6964 _local_address="$_local_address$lvalue,"
6965 shift
6966 ;;
6967 --apache)
6968 wvalue="apache"
6969 if [ -z "$_webroot" ]; then
6970 _webroot="$wvalue"
6971 else
6972 _webroot="$_webroot,$wvalue"
6973 fi
6974 ;;
6975 --nginx)
6976 wvalue="$NGINX"
6977 if [ "$2" ] && ! _startswith "$2" "-"; then
6978 wvalue="$NGINX$2"
6979 shift
6980 fi
6981 if [ -z "$_webroot" ]; then
6982 _webroot="$wvalue"
6983 else
6984 _webroot="$_webroot,$wvalue"
6985 fi
6986 ;;
6987 --dns)
6988 wvalue="$W_DNS"
6989 if [ "$2" ] && ! _startswith "$2" "-"; then
6990 wvalue="$2"
6991 shift
6992 fi
6993 if [ -z "$_webroot" ]; then
6994 _webroot="$wvalue"
6995 else
6996 _webroot="$_webroot,$wvalue"
6997 fi
6998 ;;
6999 --dnssleep)
7000 _dnssleep="$2"
7001 Le_DNSSleep="$_dnssleep"
7002 shift
7003 ;;
7004
7005 --keylength | -k)
7006 _keylength="$2"
7007 shift
7008 ;;
7009 --accountkeylength | -ak)
7010 _accountkeylength="$2"
7011 shift
7012 ;;
7013
7014 --cert-file | --certpath)
7015 _cert_file="$2"
7016 shift
7017 ;;
7018 --key-file | --keypath)
7019 _key_file="$2"
7020 shift
7021 ;;
7022 --ca-file | --capath)
7023 _ca_file="$2"
7024 shift
7025 ;;
7026 --fullchain-file | --fullchainpath)
7027 _fullchain_file="$2"
7028 shift
7029 ;;
7030 --reloadcmd | --reloadCmd)
7031 _reloadcmd="$2"
7032 shift
7033 ;;
7034 --password)
7035 _password="$2"
7036 shift
7037 ;;
7038 --accountconf)
7039 _accountconf="$2"
7040 ACCOUNT_CONF_PATH="$_accountconf"
7041 shift
7042 ;;
7043 --home)
7044 LE_WORKING_DIR="$2"
7045 shift
7046 ;;
7047 --certhome | --cert-home)
7048 _certhome="$2"
7049 CERT_HOME="$_certhome"
7050 shift
7051 ;;
7052 --config-home)
7053 _confighome="$2"
7054 LE_CONFIG_HOME="$_confighome"
7055 shift
7056 ;;
7057 --useragent)
7058 _useragent="$2"
7059 USER_AGENT="$_useragent"
7060 shift
7061 ;;
7062 --accountemail | -m)
7063 _accountemail="$2"
7064 ACCOUNT_EMAIL="$_accountemail"
7065 shift
7066 ;;
7067 --accountkey)
7068 _accountkey="$2"
7069 ACCOUNT_KEY_PATH="$_accountkey"
7070 shift
7071 ;;
7072 --days)
7073 _days="$2"
7074 Le_RenewalDays="$_days"
7075 shift
7076 ;;
7077 --httpport)
7078 _httpport="$2"
7079 Le_HTTPPort="$_httpport"
7080 shift
7081 ;;
7082 --tlsport)
7083 _tlsport="$2"
7084 Le_TLSPort="$_tlsport"
7085 shift
7086 ;;
7087 --listraw)
7088 _listraw="raw"
7089 ;;
7090 --stopRenewOnError | --stoprenewonerror | -se)
7091 _stopRenewOnError="1"
7092 ;;
7093 --insecure)
7094 #_insecure="1"
7095 HTTPS_INSECURE="1"
7096 ;;
7097 --ca-bundle)
7098 _ca_bundle="$(_readlink "$2")"
7099 CA_BUNDLE="$_ca_bundle"
7100 shift
7101 ;;
7102 --ca-path)
7103 _ca_path="$2"
7104 CA_PATH="$_ca_path"
7105 shift
7106 ;;
7107 --nocron)
7108 _nocron="1"
7109 ;;
7110 --noprofile)
7111 _noprofile="1"
7112 ;;
7113 --no-color)
7114 export ACME_NO_COLOR=1
7115 ;;
7116 --force-color)
7117 export ACME_FORCE_COLOR=1
7118 ;;
7119 --ecc)
7120 _ecc="isEcc"
7121 ;;
7122 --csr)
7123 _csr="$2"
7124 shift
7125 ;;
7126 --pre-hook)
7127 _pre_hook="$2"
7128 shift
7129 ;;
7130 --post-hook)
7131 _post_hook="$2"
7132 shift
7133 ;;
7134 --renew-hook)
7135 _renew_hook="$2"
7136 shift
7137 ;;
7138 --deploy-hook)
7139 if [ -z "$2" ] || _startswith "$2" "-"; then
7140 _usage "Please specify a value for '--deploy-hook'"
7141 return 1
7142 fi
7143 _deploy_hook="$_deploy_hook$2,"
7144 shift
7145 ;;
7146 --ocsp-must-staple | --ocsp)
7147 Le_OCSP_Staple="1"
7148 ;;
7149 --always-force-new-domain-key)
7150 if [ -z "$2" ] || _startswith "$2" "-"; then
7151 Le_ForceNewDomainKey=1
7152 else
7153 Le_ForceNewDomainKey="$2"
7154 shift
7155 fi
7156 ;;
7157 --yes-I-know-dns-manual-mode-enough-go-ahead-please)
7158 export FORCE_DNS_MANUAL=1
7159 ;;
7160 --log | --logfile)
7161 _log="1"
7162 _logfile="$2"
7163 if _startswith "$_logfile" '-'; then
7164 _logfile=""
7165 else
7166 shift
7167 fi
7168 LOG_FILE="$_logfile"
7169 if [ -z "$LOG_LEVEL" ]; then
7170 LOG_LEVEL="$DEFAULT_LOG_LEVEL"
7171 fi
7172 ;;
7173 --log-level)
7174 _log_level="$2"
7175 LOG_LEVEL="$_log_level"
7176 shift
7177 ;;
7178 --syslog)
7179 if ! _startswith "$2" '-'; then
7180 _syslog="$2"
7181 shift
7182 fi
7183 if [ -z "$_syslog" ]; then
7184 _syslog="$SYSLOG_LEVEL_DEFAULT"
7185 fi
7186 ;;
7187 --auto-upgrade)
7188 _auto_upgrade="$2"
7189 if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then
7190 _auto_upgrade="1"
7191 else
7192 shift
7193 fi
7194 AUTO_UPGRADE="$_auto_upgrade"
7195 ;;
7196 --listen-v4)
7197 _listen_v4="1"
7198 Le_Listen_V4="$_listen_v4"
7199 ;;
7200 --listen-v6)
7201 _listen_v6="1"
7202 Le_Listen_V6="$_listen_v6"
7203 ;;
7204 --openssl-bin)
7205 _openssl_bin="$2"
7206 ACME_OPENSSL_BIN="$_openssl_bin"
7207 shift
7208 ;;
7209 --use-wget)
7210 _use_wget="1"
7211 ACME_USE_WGET="1"
7212 ;;
7213 --branch | -b)
7214 export BRANCH="$2"
7215 shift
7216 ;;
7217 --notify-hook)
7218 _nhook="$2"
7219 if _startswith "$_nhook" "-"; then
7220 _err "'$_nhook' is not a hook name for '$1'"
7221 return 1
7222 fi
7223 if [ "$_notify_hook" ]; then
7224 _notify_hook="$_notify_hook,$_nhook"
7225 else
7226 _notify_hook="$_nhook"
7227 fi
7228 shift
7229 ;;
7230 --notify-level)
7231 _nlevel="$2"
7232 if _startswith "$_nlevel" "-"; then
7233 _err "'$_nlevel' is not a integer for '$1'"
7234 return 1
7235 fi
7236 _notify_level="$_nlevel"
7237 shift
7238 ;;
7239 --notify-mode)
7240 _nmode="$2"
7241 if _startswith "$_nmode" "-"; then
7242 _err "'$_nmode' is not a integer for '$1'"
7243 return 1
7244 fi
7245 _notify_mode="$_nmode"
7246 shift
7247 ;;
7248 --revoke-reason)
7249 _revoke_reason="$2"
7250 if _startswith "$_revoke_reason" "-"; then
7251 _err "'$_revoke_reason' is not a integer for '$1'"
7252 return 1
7253 fi
7254 shift
7255 ;;
7256 --eab-kid)
7257 _eab_kid="$2"
7258 shift
7259 ;;
7260 --eab-hmac-key)
7261 _eab_hmac_key="$2"
7262 shift
7263 ;;
7264 --preferred-chain)
7265 _preferred_chain="$2"
7266 shift
7267 ;;
7268 *)
7269 _err "Unknown parameter : $1"
7270 return 1
7271 ;;
7272 esac
7273
7274 shift 1
7275 done
7276
7277 if [ "${_CMD}" != "install" ]; then
7278 if [ "$__INTERACTIVE" ] && ! _checkSudo; then
7279 if [ -z "$FORCE" ]; then
7280 #Use "echo" here, instead of _info. it's too early
7281 echo "It seems that you are using sudo, please read this link first:"
7282 echo "$_SUDO_WIKI"
7283 return 1
7284 fi
7285 fi
7286 __initHome
7287 if [ "$_log" ]; then
7288 if [ -z "$_logfile" ]; then
7289 _logfile="$DEFAULT_LOG_FILE"
7290 fi
7291 fi
7292 if [ "$_logfile" ]; then
7293 _saveaccountconf "LOG_FILE" "$_logfile"
7294 LOG_FILE="$_logfile"
7295 fi
7296
7297 if [ "$_log_level" ]; then
7298 _saveaccountconf "LOG_LEVEL" "$_log_level"
7299 LOG_LEVEL="$_log_level"
7300 fi
7301
7302 if [ "$_syslog" ]; then
7303 if _exists logger; then
7304 if [ "$_syslog" = "0" ]; then
7305 _clearaccountconf "SYS_LOG"
7306 else
7307 _saveaccountconf "SYS_LOG" "$_syslog"
7308 fi
7309 SYS_LOG="$_syslog"
7310 else
7311 _err "The 'logger' command is not found, can not enable syslog."
7312 _clearaccountconf "SYS_LOG"
7313 SYS_LOG=""
7314 fi
7315 fi
7316
7317 _processAccountConf
7318 fi
7319
7320 _debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
7321
7322 if [ "$DEBUG" ]; then
7323 version
7324 if [ "$_server" ]; then
7325 _debug "Using server: $_server"
7326 fi
7327 fi
7328 _debug "Running cmd: ${_CMD}"
7329 case "${_CMD}" in
7330 install) install "$_nocron" "$_confighome" "$_noprofile" ;;
7331 uninstall) uninstall "$_nocron" ;;
7332 upgrade) upgrade ;;
7333 issue)
7334 issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain"
7335 ;;
7336 deploy)
7337 deploy "$_domain" "$_deploy_hook" "$_ecc"
7338 ;;
7339 signcsr)
7340 signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias"
7341 ;;
7342 showcsr)
7343 showcsr "$_csr" "$_domain"
7344 ;;
7345 installcert)
7346 installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc"
7347 ;;
7348 renew)
7349 renew "$_domain" "$_ecc"
7350 ;;
7351 renewAll)
7352 renewAll "$_stopRenewOnError"
7353 ;;
7354 revoke)
7355 revoke "$_domain" "$_ecc" "$_revoke_reason"
7356 ;;
7357 remove)
7358 remove "$_domain" "$_ecc"
7359 ;;
7360 deactivate)
7361 deactivate "$_domain,$_altdomains"
7362 ;;
7363 registeraccount)
7364 registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"
7365 ;;
7366 updateaccount)
7367 updateaccount
7368 ;;
7369 deactivateaccount)
7370 deactivateaccount
7371 ;;
7372 list)
7373 list "$_listraw" "$_domain"
7374 ;;
7375 installcronjob) installcronjob "$_confighome" ;;
7376 uninstallcronjob) uninstallcronjob ;;
7377 cron) cron ;;
7378 toPkcs)
7379 toPkcs "$_domain" "$_password" "$_ecc"
7380 ;;
7381 toPkcs8)
7382 toPkcs8 "$_domain" "$_ecc"
7383 ;;
7384 createAccountKey)
7385 createAccountKey "$_accountkeylength"
7386 ;;
7387 createDomainKey)
7388 createDomainKey "$_domain" "$_keylength"
7389 ;;
7390 createCSR)
7391 createCSR "$_domain" "$_altdomains" "$_ecc"
7392 ;;
7393 setnotify)
7394 setnotify "$_notify_hook" "$_notify_level" "$_notify_mode"
7395 ;;
7396 setdefaultca)
7397 setdefaultca
7398 ;;
7399 *)
7400 if [ "$_CMD" ]; then
7401 _err "Invalid command: $_CMD"
7402 fi
7403 showhelp
7404 return 1
7405 ;;
7406 esac
7407 _ret="$?"
7408 if [ "$_ret" != "0" ]; then
7409 return $_ret
7410 fi
7411
7412 if [ "${_CMD}" = "install" ]; then
7413 if [ "$_log" ]; then
7414 if [ -z "$LOG_FILE" ]; then
7415 LOG_FILE="$DEFAULT_LOG_FILE"
7416 fi
7417 _saveaccountconf "LOG_FILE" "$LOG_FILE"
7418 fi
7419
7420 if [ "$_log_level" ]; then
7421 _saveaccountconf "LOG_LEVEL" "$_log_level"
7422 fi
7423
7424 if [ "$_syslog" ]; then
7425 if _exists logger; then
7426 if [ "$_syslog" = "0" ]; then
7427 _clearaccountconf "SYS_LOG"
7428 else
7429 _saveaccountconf "SYS_LOG" "$_syslog"
7430 fi
7431 else
7432 _err "The 'logger' command is not found, can not enable syslog."
7433 _clearaccountconf "SYS_LOG"
7434 SYS_LOG=""
7435 fi
7436 fi
7437
7438 _processAccountConf
7439 fi
7440
7441 }
7442
7443 if [ "$INSTALLONLINE" ]; then
7444 INSTALLONLINE=""
7445 _installOnline
7446 exit
7447 fi
7448
7449 main() {
7450 [ -z "$1" ] && showhelp && return
7451 if _startswith "$1" '-'; then _process "$@"; else "$@"; fi
7452 }
7453
7454 main "$@"