]> git.proxmox.com Git - mirror_acme.sh.git/blob - acme.sh
minor
[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)"
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 _debug2 "response" "$response"
2507
2508 ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'key-change" *: *"[^"]*"' | cut -d '"' -f 3)
2509 if [ -z "$ACME_KEY_CHANGE" ]; then
2510 ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3)
2511 fi
2512 export ACME_KEY_CHANGE
2513
2514 ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'new-authz" *: *"[^"]*"' | cut -d '"' -f 3)
2515 if [ -z "$ACME_NEW_AUTHZ" ]; then
2516 ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3)
2517 fi
2518 export ACME_NEW_AUTHZ
2519
2520 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3)
2521 ACME_NEW_ORDER_RES="new-cert"
2522 if [ -z "$ACME_NEW_ORDER" ]; then
2523 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
2524 ACME_NEW_ORDER_RES="new-order"
2525 if [ -z "$ACME_NEW_ORDER" ]; then
2526 ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3)
2527 fi
2528 fi
2529 export ACME_NEW_ORDER
2530 export ACME_NEW_ORDER_RES
2531
2532 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3)
2533 ACME_NEW_ACCOUNT_RES="new-reg"
2534 if [ -z "$ACME_NEW_ACCOUNT" ]; then
2535 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
2536 ACME_NEW_ACCOUNT_RES="new-account"
2537 if [ -z "$ACME_NEW_ACCOUNT" ]; then
2538 ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3)
2539 if [ "$ACME_NEW_ACCOUNT" ]; then
2540 export ACME_VERSION=2
2541 fi
2542 fi
2543 fi
2544 export ACME_NEW_ACCOUNT
2545 export ACME_NEW_ACCOUNT_RES
2546
2547 ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
2548 if [ -z "$ACME_REVOKE_CERT" ]; then
2549 ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3)
2550 fi
2551 export ACME_REVOKE_CERT
2552
2553 ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3)
2554 if [ -z "$ACME_NEW_NONCE" ]; then
2555 ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3)
2556 fi
2557 export ACME_NEW_NONCE
2558
2559 ACME_AGREEMENT=$(echo "$response" | _egrep_o 'terms-of-service" *: *"[^"]*"' | cut -d '"' -f 3)
2560 if [ -z "$ACME_AGREEMENT" ]; then
2561 ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
2562 fi
2563 export ACME_AGREEMENT
2564
2565 _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
2566 _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ"
2567 _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER"
2568 _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT"
2569 _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
2570 _debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
2571 _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE"
2572 _debug "ACME_VERSION" "$ACME_VERSION"
2573
2574 fi
2575 }
2576
2577 #[domain] [keylength or isEcc flag]
2578 _initpath() {
2579 domain="$1"
2580 _ilength="$2"
2581
2582 __initHome
2583
2584 if [ -f "$ACCOUNT_CONF_PATH" ]; then
2585 . "$ACCOUNT_CONF_PATH"
2586 fi
2587
2588 if [ "$_ACME_IN_CRON" ]; then
2589 if [ ! "$_USER_PATH_EXPORTED" ]; then
2590 _USER_PATH_EXPORTED=1
2591 export PATH="$USER_PATH:$PATH"
2592 fi
2593 fi
2594
2595 if [ -z "$CA_HOME" ]; then
2596 CA_HOME="$DEFAULT_CA_HOME"
2597 fi
2598
2599 if [ -z "$ACME_DIRECTORY" ]; then
2600 if [ "$STAGE" ]; then
2601 ACME_DIRECTORY="$DEFAULT_STAGING_CA"
2602 _info "Using ACME_DIRECTORY: $ACME_DIRECTORY"
2603 else
2604 default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER")
2605 _debug default_acme_server "$default_acme_server"
2606 if [ "$default_acme_server" ]; then
2607 ACME_DIRECTORY="$default_acme_server"
2608 else
2609 ACME_DIRECTORY="$DEFAULT_CA"
2610 fi
2611 fi
2612 fi
2613
2614 _debug ACME_DIRECTORY "$ACME_DIRECTORY"
2615 _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
2616 _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST"
2617
2618 CA_DIR="$CA_HOME/$_ACME_SERVER_HOST"
2619
2620 _DEFAULT_CA_CONF="$CA_DIR/ca.conf"
2621
2622 if [ -z "$CA_CONF" ]; then
2623 CA_CONF="$_DEFAULT_CA_CONF"
2624 fi
2625 _debug3 CA_CONF "$CA_CONF"
2626
2627 if [ -f "$CA_CONF" ]; then
2628 . "$CA_CONF"
2629 fi
2630
2631 if [ -z "$ACME_DIR" ]; then
2632 ACME_DIR="/home/.acme"
2633 fi
2634
2635 if [ -z "$APACHE_CONF_BACKUP_DIR" ]; then
2636 APACHE_CONF_BACKUP_DIR="$LE_CONFIG_HOME"
2637 fi
2638
2639 if [ -z "$USER_AGENT" ]; then
2640 USER_AGENT="$DEFAULT_USER_AGENT"
2641 fi
2642
2643 if [ -z "$HTTP_HEADER" ]; then
2644 HTTP_HEADER="$LE_CONFIG_HOME/http.header"
2645 fi
2646
2647 _OLD_ACCOUNT_KEY="$LE_WORKING_DIR/account.key"
2648 _OLD_ACCOUNT_JSON="$LE_WORKING_DIR/account.json"
2649
2650 _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key"
2651 _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json"
2652 if [ -z "$ACCOUNT_KEY_PATH" ]; then
2653 ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH"
2654 fi
2655
2656 if [ -z "$ACCOUNT_JSON_PATH" ]; then
2657 ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH"
2658 fi
2659
2660 _DEFAULT_CERT_HOME="$LE_CONFIG_HOME"
2661 if [ -z "$CERT_HOME" ]; then
2662 CERT_HOME="$_DEFAULT_CERT_HOME"
2663 fi
2664
2665 if [ -z "$ACME_OPENSSL_BIN" ] || [ ! -f "$ACME_OPENSSL_BIN" ] || [ ! -x "$ACME_OPENSSL_BIN" ]; then
2666 ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN"
2667 fi
2668
2669 if [ -z "$domain" ]; then
2670 return 0
2671 fi
2672
2673 if [ -z "$DOMAIN_PATH" ]; then
2674 domainhome="$CERT_HOME/$domain"
2675 domainhomeecc="$CERT_HOME/$domain$ECC_SUFFIX"
2676
2677 DOMAIN_PATH="$domainhome"
2678
2679 if _isEccKey "$_ilength"; then
2680 DOMAIN_PATH="$domainhomeecc"
2681 else
2682 if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then
2683 _info "The domain '$domain' seems to have a ECC cert already, please add '$(__red "--ecc")' parameter if you want to use that cert."
2684 fi
2685 fi
2686 _debug DOMAIN_PATH "$DOMAIN_PATH"
2687 fi
2688
2689 if [ -z "$DOMAIN_BACKUP_PATH" ]; then
2690 DOMAIN_BACKUP_PATH="$DOMAIN_PATH/backup"
2691 fi
2692
2693 if [ -z "$DOMAIN_CONF" ]; then
2694 DOMAIN_CONF="$DOMAIN_PATH/$domain.conf"
2695 fi
2696
2697 if [ -z "$DOMAIN_SSL_CONF" ]; then
2698 DOMAIN_SSL_CONF="$DOMAIN_PATH/$domain.csr.conf"
2699 fi
2700
2701 if [ -z "$CSR_PATH" ]; then
2702 CSR_PATH="$DOMAIN_PATH/$domain.csr"
2703 fi
2704 if [ -z "$CERT_KEY_PATH" ]; then
2705 CERT_KEY_PATH="$DOMAIN_PATH/$domain.key"
2706 fi
2707 if [ -z "$CERT_PATH" ]; then
2708 CERT_PATH="$DOMAIN_PATH/$domain.cer"
2709 fi
2710 if [ -z "$CA_CERT_PATH" ]; then
2711 CA_CERT_PATH="$DOMAIN_PATH/ca.cer"
2712 fi
2713 if [ -z "$CERT_FULLCHAIN_PATH" ]; then
2714 CERT_FULLCHAIN_PATH="$DOMAIN_PATH/fullchain.cer"
2715 fi
2716 if [ -z "$CERT_PFX_PATH" ]; then
2717 CERT_PFX_PATH="$DOMAIN_PATH/$domain.pfx"
2718 fi
2719 if [ -z "$CERT_PKCS8_PATH" ]; then
2720 CERT_PKCS8_PATH="$DOMAIN_PATH/$domain.pkcs8"
2721 fi
2722
2723 if [ -z "$TLS_CONF" ]; then
2724 TLS_CONF="$DOMAIN_PATH/tls.validation.conf"
2725 fi
2726 if [ -z "$TLS_CERT" ]; then
2727 TLS_CERT="$DOMAIN_PATH/tls.validation.cert"
2728 fi
2729 if [ -z "$TLS_KEY" ]; then
2730 TLS_KEY="$DOMAIN_PATH/tls.validation.key"
2731 fi
2732 if [ -z "$TLS_CSR" ]; then
2733 TLS_CSR="$DOMAIN_PATH/tls.validation.csr"
2734 fi
2735
2736 }
2737
2738 _exec() {
2739 if [ -z "$_EXEC_TEMP_ERR" ]; then
2740 _EXEC_TEMP_ERR="$(_mktemp)"
2741 fi
2742
2743 if [ "$_EXEC_TEMP_ERR" ]; then
2744 eval "$@ 2>>$_EXEC_TEMP_ERR"
2745 else
2746 eval "$@"
2747 fi
2748 }
2749
2750 _exec_err() {
2751 [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR"
2752 }
2753
2754 _apachePath() {
2755 _APACHECTL="apachectl"
2756 if ! _exists apachectl; then
2757 if _exists apache2ctl; then
2758 _APACHECTL="apache2ctl"
2759 else
2760 _err "'apachectl not found. It seems that apache is not installed, or you are not root user.'"
2761 _err "Please use webroot mode to try again."
2762 return 1
2763 fi
2764 fi
2765
2766 if ! _exec $_APACHECTL -V >/dev/null; then
2767 _exec_err
2768 return 1
2769 fi
2770
2771 if [ "$APACHE_HTTPD_CONF" ]; then
2772 _saveaccountconf APACHE_HTTPD_CONF "$APACHE_HTTPD_CONF"
2773 httpdconf="$APACHE_HTTPD_CONF"
2774 httpdconfname="$(basename "$httpdconfname")"
2775 else
2776 httpdconfname="$($_APACHECTL -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"')"
2777 _debug httpdconfname "$httpdconfname"
2778
2779 if [ -z "$httpdconfname" ]; then
2780 _err "Can not read apache config file."
2781 return 1
2782 fi
2783
2784 if _startswith "$httpdconfname" '/'; then
2785 httpdconf="$httpdconfname"
2786 httpdconfname="$(basename "$httpdconfname")"
2787 else
2788 httpdroot="$($_APACHECTL -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"')"
2789 _debug httpdroot "$httpdroot"
2790 httpdconf="$httpdroot/$httpdconfname"
2791 httpdconfname="$(basename "$httpdconfname")"
2792 fi
2793 fi
2794 _debug httpdconf "$httpdconf"
2795 _debug httpdconfname "$httpdconfname"
2796 if [ ! -f "$httpdconf" ]; then
2797 _err "Apache Config file not found" "$httpdconf"
2798 return 1
2799 fi
2800 return 0
2801 }
2802
2803 _restoreApache() {
2804 if [ -z "$usingApache" ]; then
2805 return 0
2806 fi
2807 _initpath
2808 if ! _apachePath; then
2809 return 1
2810 fi
2811
2812 if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]; then
2813 _debug "No config file to restore."
2814 return 0
2815 fi
2816
2817 cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf"
2818 _debug "Restored: $httpdconf."
2819 if ! _exec $_APACHECTL -t; then
2820 _exec_err
2821 _err "Sorry, restore apache config error, please contact me."
2822 return 1
2823 fi
2824 _debug "Restored successfully."
2825 rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
2826 return 0
2827 }
2828
2829 _setApache() {
2830 _initpath
2831 if ! _apachePath; then
2832 return 1
2833 fi
2834
2835 #test the conf first
2836 _info "Checking if there is an error in the apache config file before starting."
2837
2838 if ! _exec "$_APACHECTL" -t >/dev/null; then
2839 _exec_err
2840 _err "The apache config file has error, please fix it first, then try again."
2841 _err "Don't worry, there is nothing changed to your system."
2842 return 1
2843 else
2844 _info "OK"
2845 fi
2846
2847 #backup the conf
2848 _debug "Backup apache config file" "$httpdconf"
2849 if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then
2850 _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed."
2851 _err "This might be a bug of $PROJECT_NAME , please report issue: $PROJECT"
2852 return 1
2853 fi
2854 _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
2855 _info "In case there is an error that can not be restored automatically, you may try restore it yourself."
2856 _info "The backup file will be deleted on success, just forget it."
2857
2858 #add alias
2859
2860 apacheVer="$($_APACHECTL -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2)"
2861 _debug "apacheVer" "$apacheVer"
2862 apacheMajor="$(echo "$apacheVer" | cut -d . -f 1)"
2863 apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
2864
2865 if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then
2866 echo "
2867 Alias /.well-known/acme-challenge $ACME_DIR
2868
2869 <Directory $ACME_DIR >
2870 Require all granted
2871 </Directory>
2872 " >>"$httpdconf"
2873 else
2874 echo "
2875 Alias /.well-known/acme-challenge $ACME_DIR
2876
2877 <Directory $ACME_DIR >
2878 Order allow,deny
2879 Allow from all
2880 </Directory>
2881 " >>"$httpdconf"
2882 fi
2883
2884 _msg="$($_APACHECTL -t 2>&1)"
2885 if [ "$?" != "0" ]; then
2886 _err "Sorry, apache config error"
2887 if _restoreApache; then
2888 _err "The apache config file is restored."
2889 else
2890 _err "Sorry, The apache config file can not be restored, please report bug."
2891 fi
2892 return 1
2893 fi
2894
2895 if [ ! -d "$ACME_DIR" ]; then
2896 mkdir -p "$ACME_DIR"
2897 chmod 755 "$ACME_DIR"
2898 fi
2899
2900 if ! _exec "$_APACHECTL" graceful; then
2901 _exec_err
2902 _err "$_APACHECTL graceful error, please contact me."
2903 _restoreApache
2904 return 1
2905 fi
2906 usingApache="1"
2907 return 0
2908 }
2909
2910 #find the real nginx conf file
2911 #backup
2912 #set the nginx conf
2913 #returns the real nginx conf file
2914 _setNginx() {
2915 _d="$1"
2916 _croot="$2"
2917 _thumbpt="$3"
2918
2919 FOUND_REAL_NGINX_CONF=""
2920 FOUND_REAL_NGINX_CONF_LN=""
2921 BACKUP_NGINX_CONF=""
2922 _debug _croot "$_croot"
2923 _start_f="$(echo "$_croot" | cut -d : -f 2)"
2924 _debug _start_f "$_start_f"
2925 if [ -z "$_start_f" ]; then
2926 _debug "find start conf from nginx command"
2927 if [ -z "$NGINX_CONF" ]; then
2928 if ! _exists "nginx"; then
2929 _err "nginx command is not found."
2930 return 1
2931 fi
2932 NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")"
2933 _debug NGINX_CONF "$NGINX_CONF"
2934 NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)"
2935 _debug NGINX_CONF "$NGINX_CONF"
2936 if [ -z "$NGINX_CONF" ]; then
2937 _err "Can not find nginx conf."
2938 NGINX_CONF=""
2939 return 1
2940 fi
2941 if [ ! -f "$NGINX_CONF" ]; then
2942 _err "'$NGINX_CONF' doesn't exist."
2943 NGINX_CONF=""
2944 return 1
2945 fi
2946 _debug "Found nginx conf file:$NGINX_CONF"
2947 fi
2948 _start_f="$NGINX_CONF"
2949 fi
2950 _debug "Start detect nginx conf for $_d from:$_start_f"
2951 if ! _checkConf "$_d" "$_start_f"; then
2952 _err "Can not find conf file for domain $d"
2953 return 1
2954 fi
2955 _info "Found conf file: $FOUND_REAL_NGINX_CONF"
2956
2957 _ln=$FOUND_REAL_NGINX_CONF_LN
2958 _debug "_ln" "$_ln"
2959
2960 _lnn=$(_math $_ln + 1)
2961 _debug _lnn "$_lnn"
2962 _start_tag="$(sed -n "$_lnn,${_lnn}p" "$FOUND_REAL_NGINX_CONF")"
2963 _debug "_start_tag" "$_start_tag"
2964 if [ "$_start_tag" = "$NGINX_START" ]; then
2965 _info "The domain $_d is already configured, skip"
2966 FOUND_REAL_NGINX_CONF=""
2967 return 0
2968 fi
2969
2970 mkdir -p "$DOMAIN_BACKUP_PATH"
2971 _backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf"
2972 _debug _backup_conf "$_backup_conf"
2973 BACKUP_NGINX_CONF="$_backup_conf"
2974 _info "Backup $FOUND_REAL_NGINX_CONF to $_backup_conf"
2975 if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then
2976 _err "backup error."
2977 FOUND_REAL_NGINX_CONF=""
2978 return 1
2979 fi
2980
2981 if ! _exists "nginx"; then
2982 _err "nginx command is not found."
2983 return 1
2984 fi
2985 _info "Check the nginx conf before setting up."
2986 if ! _exec "nginx -t" >/dev/null; then
2987 _exec_err
2988 return 1
2989 fi
2990
2991 _info "OK, Set up nginx config file"
2992
2993 if ! sed -n "1,${_ln}p" "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"; then
2994 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
2995 _err "write nginx conf error, but don't worry, the file is restored to the original version."
2996 return 1
2997 fi
2998
2999 echo "$NGINX_START
3000 location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
3001 default_type text/plain;
3002 return 200 \"\$1.$_thumbpt\";
3003 }
3004 #NGINX_START
3005 " >>"$FOUND_REAL_NGINX_CONF"
3006
3007 if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then
3008 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3009 _err "write nginx conf error, but don't worry, the file is restored."
3010 return 1
3011 fi
3012 _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)"
3013 _info "nginx conf is done, let's check it again."
3014 if ! _exec "nginx -t" >/dev/null; then
3015 _exec_err
3016 _err "It seems that nginx conf was broken, let's restore."
3017 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3018 return 1
3019 fi
3020
3021 _info "Reload nginx"
3022 if ! _exec "nginx -s reload" >/dev/null; then
3023 _exec_err
3024 _err "It seems that nginx reload error, let's restore."
3025 cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"
3026 return 1
3027 fi
3028
3029 return 0
3030 }
3031
3032 #d , conf
3033 _checkConf() {
3034 _d="$1"
3035 _c_file="$2"
3036 _debug "Start _checkConf from:$_c_file"
3037 if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
3038 _debug "wildcard"
3039 for _w_f in $2; do
3040 if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then
3041 return 0
3042 fi
3043 done
3044 #not found
3045 return 1
3046 elif [ -f "$2" ]; then
3047 _debug "single"
3048 if _isRealNginxConf "$1" "$2"; then
3049 _debug "$2 is found."
3050 FOUND_REAL_NGINX_CONF="$2"
3051 return 0
3052 fi
3053 if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then
3054 _debug "Try include files"
3055 for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do
3056 _debug "check included $included"
3057 if _checkConf "$1" "$included"; then
3058 return 0
3059 fi
3060 done
3061 fi
3062 return 1
3063 else
3064 _debug "$2 not found."
3065 return 1
3066 fi
3067 return 1
3068 }
3069
3070 #d , conf
3071 _isRealNginxConf() {
3072 _debug "_isRealNginxConf $1 $2"
3073 if [ -f "$2" ]; then
3074 for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do
3075 _debug _fln "$_fln"
3076 if [ "$_fln" ]; then
3077 _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *" | grep -v server_name | _tail_n 1)
3078 _debug "_start" "$_start"
3079 _start_n=$(echo "$_start" | cut -d : -f 1)
3080 _start_nn=$(_math $_start_n + 1)
3081 _debug "_start_n" "$_start_n"
3082 _debug "_start_nn" "$_start_nn"
3083
3084 _left="$(sed -n "${_start_nn},99999p" "$2")"
3085 _debug2 _left "$_left"
3086 _end="$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | grep -v server_name | _head_n 1)"
3087 _debug "_end" "$_end"
3088 if [ "$_end" ]; then
3089 _end_n=$(echo "$_end" | cut -d : -f 1)
3090 _debug "_end_n" "$_end_n"
3091 _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
3092 else
3093 _seg_n="$_left"
3094 fi
3095
3096 _debug "_seg_n" "$_seg_n"
3097
3098 _skip_ssl=1
3099 for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do
3100 if [ "$_listen_i" ]; then
3101 if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl")" ]; then
3102 _debug2 "$_listen_i is ssl"
3103 else
3104 _debug2 "$_listen_i is plain text"
3105 _skip_ssl=""
3106 break
3107 fi
3108 fi
3109 done
3110
3111 if [ "$_skip_ssl" = "1" ]; then
3112 _debug "ssl on, skip"
3113 else
3114 FOUND_REAL_NGINX_CONF_LN=$_fln
3115 _debug3 "found FOUND_REAL_NGINX_CONF_LN" "$FOUND_REAL_NGINX_CONF_LN"
3116 return 0
3117 fi
3118 fi
3119 done
3120 fi
3121 return 1
3122 }
3123
3124 #restore all the nginx conf
3125 _restoreNginx() {
3126 if [ -z "$NGINX_RESTORE_VLIST" ]; then
3127 _debug "No need to restore nginx, skip."
3128 return
3129 fi
3130 _debug "_restoreNginx"
3131 _debug "NGINX_RESTORE_VLIST" "$NGINX_RESTORE_VLIST"
3132
3133 for ng_entry in $(echo "$NGINX_RESTORE_VLIST" | tr "$dvsep" ' '); do
3134 _debug "ng_entry" "$ng_entry"
3135 _nd=$(echo "$ng_entry" | cut -d "$sep" -f 1)
3136 _ngconf=$(echo "$ng_entry" | cut -d "$sep" -f 2)
3137 _ngbackupconf=$(echo "$ng_entry" | cut -d "$sep" -f 3)
3138 _info "Restoring from $_ngbackupconf to $_ngconf"
3139 cat "$_ngbackupconf" >"$_ngconf"
3140 done
3141
3142 _info "Reload nginx"
3143 if ! _exec "nginx -s reload" >/dev/null; then
3144 _exec_err
3145 _err "It seems that nginx reload error, please report bug."
3146 return 1
3147 fi
3148 return 0
3149 }
3150
3151 _clearup() {
3152 _stopserver "$serverproc"
3153 serverproc=""
3154 _restoreApache
3155 _restoreNginx
3156 _clearupdns
3157 if [ -z "$DEBUG" ]; then
3158 rm -f "$TLS_CONF"
3159 rm -f "$TLS_CERT"
3160 rm -f "$TLS_KEY"
3161 rm -f "$TLS_CSR"
3162 fi
3163 }
3164
3165 _clearupdns() {
3166 _debug "_clearupdns"
3167 _debug "dns_entries" "$dns_entries"
3168
3169 if [ -z "$dns_entries" ]; then
3170 _debug "skip dns."
3171 return
3172 fi
3173 _info "Removing DNS records."
3174
3175 for entry in $dns_entries; do
3176 d=$(_getfield "$entry" 1)
3177 txtdomain=$(_getfield "$entry" 2)
3178 aliasDomain=$(_getfield "$entry" 3)
3179 _currentRoot=$(_getfield "$entry" 4)
3180 txt=$(_getfield "$entry" 5)
3181 d_api=$(_getfield "$entry" 6)
3182 _debug "d" "$d"
3183 _debug "txtdomain" "$txtdomain"
3184 _debug "aliasDomain" "$aliasDomain"
3185 _debug "_currentRoot" "$_currentRoot"
3186 _debug "txt" "$txt"
3187 _debug "d_api" "$d_api"
3188 if [ "$d_api" = "$txt" ]; then
3189 d_api=""
3190 fi
3191
3192 if [ -z "$d_api" ]; then
3193 _info "Not Found domain api file: $d_api"
3194 continue
3195 fi
3196
3197 if [ "$aliasDomain" ]; then
3198 txtdomain="$aliasDomain"
3199 fi
3200
3201 (
3202 if ! . "$d_api"; then
3203 _err "Load file $d_api error. Please check your api file and try again."
3204 return 1
3205 fi
3206
3207 rmcommand="${_currentRoot}_rm"
3208 if ! _exists "$rmcommand"; then
3209 _err "It seems that your api file doesn't define $rmcommand"
3210 return 1
3211 fi
3212 _info "Removing txt: $txt for domain: $txtdomain"
3213 if ! $rmcommand "$txtdomain" "$txt"; then
3214 _err "Error removing txt for domain:$txtdomain"
3215 return 1
3216 fi
3217 _info "Removed: Success"
3218 )
3219
3220 done
3221 }
3222
3223 # webroot removelevel tokenfile
3224 _clearupwebbroot() {
3225 __webroot="$1"
3226 if [ -z "$__webroot" ]; then
3227 _debug "no webroot specified, skip"
3228 return 0
3229 fi
3230
3231 _rmpath=""
3232 if [ "$2" = '1' ]; then
3233 _rmpath="$__webroot/.well-known"
3234 elif [ "$2" = '2' ]; then
3235 _rmpath="$__webroot/.well-known/acme-challenge"
3236 elif [ "$2" = '3' ]; then
3237 _rmpath="$__webroot/.well-known/acme-challenge/$3"
3238 else
3239 _debug "Skip for removelevel:$2"
3240 fi
3241
3242 if [ "$_rmpath" ]; then
3243 if [ "$DEBUG" ]; then
3244 _debug "Debugging, skip removing: $_rmpath"
3245 else
3246 rm -rf "$_rmpath"
3247 fi
3248 fi
3249
3250 return 0
3251
3252 }
3253
3254 _on_before_issue() {
3255 _chk_web_roots="$1"
3256 _chk_main_domain="$2"
3257 _chk_alt_domains="$3"
3258 _chk_pre_hook="$4"
3259 _chk_local_addr="$5"
3260 _debug _on_before_issue
3261 _debug _chk_main_domain "$_chk_main_domain"
3262 _debug _chk_alt_domains "$_chk_alt_domains"
3263 #run pre hook
3264 if [ "$_chk_pre_hook" ]; then
3265 _info "Run pre hook:'$_chk_pre_hook'"
3266 if ! (
3267 cd "$DOMAIN_PATH" && eval "$_chk_pre_hook"
3268 ); then
3269 _err "Error when run pre hook."
3270 return 1
3271 fi
3272 fi
3273
3274 if _hasfield "$_chk_web_roots" "$NO_VALUE"; then
3275 if ! _exists "socat"; then
3276 _err "Please install socat tools first."
3277 return 1
3278 fi
3279 fi
3280
3281 _debug Le_LocalAddress "$_chk_local_addr"
3282
3283 _index=1
3284 _currentRoot=""
3285 _addrIndex=1
3286 _w_index=1
3287 while true; do
3288 d="$(echo "$_chk_main_domain,$_chk_alt_domains," | cut -d , -f "$_w_index")"
3289 _w_index="$(_math "$_w_index" + 1)"
3290 _debug d "$d"
3291 if [ -z "$d" ]; then
3292 break
3293 fi
3294 _debug "Check for domain" "$d"
3295 _currentRoot="$(_getfield "$_chk_web_roots" $_index)"
3296 _debug "_currentRoot" "$_currentRoot"
3297 _index=$(_math $_index + 1)
3298 _checkport=""
3299 if [ "$_currentRoot" = "$NO_VALUE" ]; then
3300 _info "Standalone mode."
3301 if [ -z "$Le_HTTPPort" ]; then
3302 Le_HTTPPort=80
3303 _cleardomainconf "Le_HTTPPort"
3304 else
3305 _savedomainconf "Le_HTTPPort" "$Le_HTTPPort"
3306 fi
3307 _checkport="$Le_HTTPPort"
3308 elif [ "$_currentRoot" = "$W_ALPN" ]; then
3309 _info "Standalone alpn mode."
3310 if [ -z "$Le_TLSPort" ]; then
3311 Le_TLSPort=443
3312 else
3313 _savedomainconf "Le_TLSPort" "$Le_TLSPort"
3314 fi
3315 _checkport="$Le_TLSPort"
3316 fi
3317
3318 if [ "$_checkport" ]; then
3319 _debug _checkport "$_checkport"
3320 _checkaddr="$(_getfield "$_chk_local_addr" $_addrIndex)"
3321 _debug _checkaddr "$_checkaddr"
3322
3323 _addrIndex="$(_math $_addrIndex + 1)"
3324
3325 _netprc="$(_ss "$_checkport" | grep "$_checkport")"
3326 netprc="$(echo "$_netprc" | grep "$_checkaddr")"
3327 if [ -z "$netprc" ]; then
3328 netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS")"
3329 fi
3330 if [ "$netprc" ]; then
3331 _err "$netprc"
3332 _err "tcp port $_checkport is already used by $(echo "$netprc" | cut -d : -f 4)"
3333 _err "Please stop it first"
3334 return 1
3335 fi
3336 fi
3337 done
3338
3339 if _hasfield "$_chk_web_roots" "apache"; then
3340 if ! _setApache; then
3341 _err "set up apache error. Report error to me."
3342 return 1
3343 fi
3344 else
3345 usingApache=""
3346 fi
3347
3348 }
3349
3350 _on_issue_err() {
3351 _chk_post_hook="$1"
3352 _chk_vlist="$2"
3353 _debug _on_issue_err
3354
3355 if [ "$LOG_FILE" ]; then
3356 _err "Please check log file for more details: $LOG_FILE"
3357 else
3358 _err "Please add '--debug' or '--log' to check more details."
3359 _err "See: $_DEBUG_WIKI"
3360 fi
3361
3362 #run the post hook
3363 if [ "$_chk_post_hook" ]; then
3364 _info "Run post hook:'$_chk_post_hook'"
3365 if ! (
3366 cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
3367 ); then
3368 _err "Error when run post hook."
3369 return 1
3370 fi
3371 fi
3372
3373 #trigger the validation to flush the pending authz
3374 _debug2 "_chk_vlist" "$_chk_vlist"
3375 if [ "$_chk_vlist" ]; then
3376 (
3377 _debug2 "start to deactivate authz"
3378 ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ')
3379 for ventry in $ventries; do
3380 d=$(echo "$ventry" | cut -d "$sep" -f 1)
3381 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
3382 uri=$(echo "$ventry" | cut -d "$sep" -f 3)
3383 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
3384 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
3385 __trigger_validation "$uri" "$keyauthorization"
3386 done
3387 )
3388 fi
3389
3390 if [ "$_ACME_IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then
3391 _err "$_DNS_MANUAL_ERR"
3392 fi
3393
3394 if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then
3395 _debug "$(_dlg_versions)"
3396 fi
3397
3398 }
3399
3400 _on_issue_success() {
3401 _chk_post_hook="$1"
3402 _chk_renew_hook="$2"
3403 _debug _on_issue_success
3404
3405 #run the post hook
3406 if [ "$_chk_post_hook" ]; then
3407 _info "Run post hook:'$_chk_post_hook'"
3408 if ! (
3409 export CERT_PATH
3410 export CERT_KEY_PATH
3411 export CA_CERT_PATH
3412 export CERT_FULLCHAIN_PATH
3413 export Le_Domain="$_main_domain"
3414 cd "$DOMAIN_PATH" && eval "$_chk_post_hook"
3415 ); then
3416 _err "Error when run post hook."
3417 return 1
3418 fi
3419 fi
3420
3421 #run renew hook
3422 if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then
3423 _info "Run renew hook:'$_chk_renew_hook'"
3424 if ! (
3425 export CERT_PATH
3426 export CERT_KEY_PATH
3427 export CA_CERT_PATH
3428 export CERT_FULLCHAIN_PATH
3429 export Le_Domain="$_main_domain"
3430 cd "$DOMAIN_PATH" && eval "$_chk_renew_hook"
3431 ); then
3432 _err "Error when run renew hook."
3433 return 1
3434 fi
3435 fi
3436
3437 if _hasfield "$Le_Webroot" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
3438 _err "$_DNS_MANUAL_WARN"
3439 fi
3440
3441 }
3442
3443 #account_key_length eab-kid eab-hmac-key
3444 registeraccount() {
3445 _account_key_length="$1"
3446 _eab_id="$2"
3447 _eab_hmac_key="$3"
3448 _initpath
3449 _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
3450 }
3451
3452 __calcAccountKeyHash() {
3453 [ -f "$ACCOUNT_KEY_PATH" ] && _digest sha256 <"$ACCOUNT_KEY_PATH"
3454 }
3455
3456 __calc_account_thumbprint() {
3457 printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
3458 }
3459
3460 _getAccountEmail() {
3461 if [ "$ACCOUNT_EMAIL" ]; then
3462 echo "$ACCOUNT_EMAIL"
3463 return 0
3464 fi
3465 if [ -z "$CA_EMAIL" ]; then
3466 CA_EMAIL="$(_readcaconf CA_EMAIL)"
3467 fi
3468 if [ "$CA_EMAIL" ]; then
3469 echo "$CA_EMAIL"
3470 return 0
3471 fi
3472 _readaccountconf "ACCOUNT_EMAIL"
3473 }
3474
3475 #keylength
3476 _regAccount() {
3477 _initpath
3478 _reg_length="$1"
3479 _eab_id="$2"
3480 _eab_hmac_key="$3"
3481 _debug3 _regAccount "$_regAccount"
3482 _initAPI
3483
3484 mkdir -p "$CA_DIR"
3485 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3486 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3487 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3488 fi
3489
3490 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3491 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3492 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3493 fi
3494
3495 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3496 if ! _create_account_key "$_reg_length"; then
3497 _err "Create account key error."
3498 return 1
3499 fi
3500 fi
3501
3502 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3503 return 1
3504 fi
3505 if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
3506 _savecaconf CA_EAB_KEY_ID "$_eab_id"
3507 _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
3508 fi
3509 _eab_id=$(_readcaconf "CA_EAB_KEY_ID")
3510 _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
3511 _secure_debug3 _eab_id "$_eab_id"
3512 _secure_debug3 _eab_hmac_key "$_eab_hmac_key"
3513 _email="$(_getAccountEmail)"
3514 if [ "$_email" ]; then
3515 _savecaconf "CA_EMAIL" "$_email"
3516 fi
3517 if [ "$ACME_VERSION" = "2" ]; then
3518 if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
3519 if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
3520 _info "No EAB credentials found for ZeroSSL, let's get one"
3521 if [ -z "$_email" ]; then
3522 _err "Please provide a email address for ZeroSSL account."
3523 _err "See ZeroSSL usage: $_ZEROSSL_WIKI"
3524 return 1
3525 fi
3526 _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
3527 if [ "$?" != "0" ]; then
3528 _debug2 "$_eabresp"
3529 _err "Can not get EAB credentials from ZeroSSL."
3530 return 1
3531 fi
3532 _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
3533 if [ -z "$_eab_id" ]; then
3534 _err "Can not resolve _eab_id"
3535 return 1
3536 fi
3537 _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
3538 if [ -z "$_eab_hmac_key" ]; then
3539 _err "Can not resolve _eab_hmac_key"
3540 return 1
3541 fi
3542 _savecaconf CA_EAB_KEY_ID "$_eab_id"
3543 _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
3544 fi
3545 fi
3546 if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
3547 eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
3548 _debug3 eab_protected "$eab_protected"
3549
3550 eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
3551 _debug3 eab_protected64 "$eab_protected64"
3552
3553 eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace)
3554 _debug3 eab_payload64 "$eab_payload64"
3555
3556 eab_sign_t="$eab_protected64.$eab_payload64"
3557 _debug3 eab_sign_t "$eab_sign_t"
3558
3559 key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
3560 _debug3 key_hex "$key_hex"
3561
3562 eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
3563 _debug3 eab_signature "$eab_signature"
3564
3565 externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}"
3566 _debug3 externalBinding "$externalBinding"
3567 fi
3568 if [ "$_email" ]; then
3569 email_sg="\"contact\": [\"mailto:$_email\"], "
3570 fi
3571 regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
3572 else
3573 _reg_res="$ACME_NEW_ACCOUNT_RES"
3574 regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
3575 if [ "$_email" ]; then
3576 regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
3577 fi
3578 fi
3579
3580 _info "Registering account: $ACME_DIRECTORY"
3581
3582 if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then
3583 _err "Register account Error: $response"
3584 return 1
3585 fi
3586
3587 _eabAlreadyBound=""
3588 if [ "$code" = "" ] || [ "$code" = '201' ]; then
3589 echo "$response" >"$ACCOUNT_JSON_PATH"
3590 _info "Registered"
3591 elif [ "$code" = '409' ] || [ "$code" = '200' ]; then
3592 _info "Already registered"
3593 elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then
3594 _info "Already register EAB."
3595 _eabAlreadyBound=1
3596 else
3597 _err "Register account Error: $response"
3598 return 1
3599 fi
3600
3601 if [ -z "$_eabAlreadyBound" ]; then
3602 _debug2 responseHeaders "$responseHeaders"
3603 _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")"
3604 _debug "_accUri" "$_accUri"
3605 if [ -z "$_accUri" ]; then
3606 _err "Can not find account id url."
3607 _err "$responseHeaders"
3608 return 1
3609 fi
3610 _savecaconf "ACCOUNT_URL" "$_accUri"
3611 else
3612 ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
3613 fi
3614 export ACCOUNT_URL="$_accUri"
3615
3616 CA_KEY_HASH="$(__calcAccountKeyHash)"
3617 _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
3618 _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
3619
3620 if [ "$code" = '403' ]; then
3621 _err "It seems that the account key is already deactivated, please use a new account key."
3622 return 1
3623 fi
3624
3625 ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)"
3626 _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT"
3627 }
3628
3629 #implement updateaccount
3630 updateaccount() {
3631 _initpath
3632
3633 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3634 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3635 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3636 fi
3637
3638 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3639 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3640 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3641 fi
3642
3643 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3644 _err "Account key is not found at: $ACCOUNT_KEY_PATH"
3645 return 1
3646 fi
3647
3648 _accUri=$(_readcaconf "ACCOUNT_URL")
3649 _debug _accUri "$_accUri"
3650
3651 if [ -z "$_accUri" ]; then
3652 _err "The account url is empty, please run '--update-account' first to update the account info first,"
3653 _err "Then try again."
3654 return 1
3655 fi
3656
3657 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3658 return 1
3659 fi
3660 _initAPI
3661
3662 _email="$(_getAccountEmail)"
3663 if [ "$ACME_VERSION" = "2" ]; then
3664 if [ "$ACCOUNT_EMAIL" ]; then
3665 updjson='{"contact": ["mailto:'$_email'"]}'
3666 else
3667 updjson='{"contact": []}'
3668 fi
3669 else
3670 # ACMEv1: Updates happen the same way a registration is done.
3671 # https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-6.3
3672 _regAccount
3673 return
3674 fi
3675
3676 # this part handles ACMEv2 account updates.
3677 _send_signed_request "$_accUri" "$updjson"
3678
3679 if [ "$code" = '200' ]; then
3680 echo "$response" >"$ACCOUNT_JSON_PATH"
3681 _info "account update success for $_accUri."
3682 else
3683 _info "Error. The account was not updated."
3684 return 1
3685 fi
3686 }
3687
3688 #Implement deactivate account
3689 deactivateaccount() {
3690 _initpath
3691
3692 if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
3693 _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
3694 mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
3695 fi
3696
3697 if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
3698 _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
3699 mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
3700 fi
3701
3702 if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
3703 _err "Account key is not found at: $ACCOUNT_KEY_PATH"
3704 return 1
3705 fi
3706
3707 _accUri=$(_readcaconf "ACCOUNT_URL")
3708 _debug _accUri "$_accUri"
3709
3710 if [ -z "$_accUri" ]; then
3711 _err "The account url is empty, please run '--update-account' first to update the account info first,"
3712 _err "Then try again."
3713 return 1
3714 fi
3715
3716 if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
3717 return 1
3718 fi
3719 _initAPI
3720
3721 if [ "$ACME_VERSION" = "2" ]; then
3722 _djson="{\"status\":\"deactivated\"}"
3723 else
3724 _djson="{\"resource\": \"reg\", \"status\":\"deactivated\"}"
3725 fi
3726 if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
3727 _info "Deactivate account success for $_accUri."
3728 _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
3729 elif [ "$code" = "403" ]; then
3730 _info "The account is already deactivated."
3731 _accid=$(_getfield "$_accUri" "999" "/")
3732 else
3733 _err "Deactivate: account failed for $_accUri."
3734 return 1
3735 fi
3736
3737 _debug "Account id: $_accid"
3738 if [ "$_accid" ]; then
3739 _deactivated_account_path="$CA_DIR/deactivated/$_accid"
3740 _debug _deactivated_account_path "$_deactivated_account_path"
3741 if mkdir -p "$_deactivated_account_path"; then
3742 _info "Moving deactivated account info to $_deactivated_account_path/"
3743 mv "$CA_CONF" "$_deactivated_account_path/"
3744 mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/"
3745 mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/"
3746 else
3747 _err "Can not create dir: $_deactivated_account_path, try to remove the deactivated account key."
3748 rm -f "$CA_CONF"
3749 rm -f "$ACCOUNT_JSON_PATH"
3750 rm -f "$ACCOUNT_KEY_PATH"
3751 fi
3752 fi
3753 }
3754
3755 # domain folder file
3756 _findHook() {
3757 _hookdomain="$1"
3758 _hookcat="$2"
3759 _hookname="$3"
3760
3761 if [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname" ]; then
3762 d_api="$_SCRIPT_HOME/$_hookcat/$_hookname"
3763 elif [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname.sh" ]; then
3764 d_api="$_SCRIPT_HOME/$_hookcat/$_hookname.sh"
3765 elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname" ]; then
3766 d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname"
3767 elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname.sh" ]; then
3768 d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname.sh"
3769 elif [ -f "$LE_WORKING_DIR/$_hookname" ]; then
3770 d_api="$LE_WORKING_DIR/$_hookname"
3771 elif [ -f "$LE_WORKING_DIR/$_hookname.sh" ]; then
3772 d_api="$LE_WORKING_DIR/$_hookname.sh"
3773 elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname" ]; then
3774 d_api="$LE_WORKING_DIR/$_hookcat/$_hookname"
3775 elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname.sh" ]; then
3776 d_api="$LE_WORKING_DIR/$_hookcat/$_hookname.sh"
3777 fi
3778
3779 printf "%s" "$d_api"
3780 }
3781
3782 #domain
3783 __get_domain_new_authz() {
3784 _gdnd="$1"
3785 _info "Getting new-authz for domain" "$_gdnd"
3786 _initAPI
3787 _Max_new_authz_retry_times=5
3788 _authz_i=0
3789 while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do
3790 _debug "Try new-authz for the $_authz_i time."
3791 if ! _send_signed_request "${ACME_NEW_AUTHZ}" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$(_idn "$_gdnd")\"}}"; then
3792 _err "Can not get domain new authz."
3793 return 1
3794 fi
3795 if _contains "$response" "No registration exists matching provided key"; then
3796 _err "It seems there is an error, but it's recovered now, please try again."
3797 _err "If you see this message for a second time, please report bug: $(__green "$PROJECT")"
3798 _clearcaconf "CA_KEY_HASH"
3799 break
3800 fi
3801 if ! _contains "$response" "An error occurred while processing your request"; then
3802 _info "The new-authz request is ok."
3803 break
3804 fi
3805 _authz_i="$(_math "$_authz_i" + 1)"
3806 _info "The server is busy, Sleep $_authz_i to retry."
3807 _sleep "$_authz_i"
3808 done
3809
3810 if [ "$_authz_i" = "$_Max_new_authz_retry_times" ]; then
3811 _err "new-authz retry reach the max $_Max_new_authz_retry_times times."
3812 fi
3813
3814 if [ "$code" ] && [ "$code" != '201' ]; then
3815 _err "new-authz error: $response"
3816 return 1
3817 fi
3818
3819 }
3820
3821 #uri keyAuthorization
3822 __trigger_validation() {
3823 _debug2 "Trigger domain validation."
3824 _t_url="$1"
3825 _debug2 _t_url "$_t_url"
3826 _t_key_authz="$2"
3827 _debug2 _t_key_authz "$_t_key_authz"
3828 _t_vtype="$3"
3829 _debug2 _t_vtype "$_t_vtype"
3830 if [ "$ACME_VERSION" = "2" ]; then
3831 _send_signed_request "$_t_url" "{}"
3832 else
3833 _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
3834 fi
3835 }
3836
3837 #endpoint domain type
3838 _ns_lookup_impl() {
3839 _ns_ep="$1"
3840 _ns_domain="$2"
3841 _ns_type="$3"
3842 _debug2 "_ns_ep" "$_ns_ep"
3843 _debug2 "_ns_domain" "$_ns_domain"
3844 _debug2 "_ns_type" "$_ns_type"
3845
3846 response="$(_H1="accept: application/dns-json" _get "$_ns_ep?name=$_ns_domain&type=$_ns_type")"
3847 _ret=$?
3848 _debug2 "response" "$response"
3849 if [ "$_ret" != "0" ]; then
3850 return $_ret
3851 fi
3852 _answers="$(echo "$response" | tr '{}' '<>' | _egrep_o '"Answer":\[[^]]*]' | tr '<>' '\n\n')"
3853 _debug2 "_answers" "$_answers"
3854 echo "$_answers"
3855 }
3856
3857 #domain, type
3858 _ns_lookup_cf() {
3859 _cf_ld="$1"
3860 _cf_ld_type="$2"
3861 _cf_ep="https://cloudflare-dns.com/dns-query"
3862 _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
3863 }
3864
3865 #domain, type
3866 _ns_purge_cf() {
3867 _cf_d="$1"
3868 _cf_d_type="$2"
3869 _debug "Cloudflare purge $_cf_d_type record for domain $_cf_d"
3870 _cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type"
3871 response="$(_post "" "$_cf_purl")"
3872 _debug2 response "$response"
3873 }
3874
3875 #checks if cf server is available
3876 _ns_is_available_cf() {
3877 if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then
3878 return 0
3879 else
3880 return 1
3881 fi
3882 }
3883
3884 #domain, type
3885 _ns_lookup_google() {
3886 _cf_ld="$1"
3887 _cf_ld_type="$2"
3888 _cf_ep="https://dns.google/resolve"
3889 _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
3890 }
3891
3892 #domain, type
3893 _ns_lookup() {
3894 if [ -z "$DOH_USE" ]; then
3895 _debug "Detect dns server first."
3896 if _ns_is_available_cf; then
3897 _debug "Use cloudflare doh server"
3898 export DOH_USE=$DOH_CLOUDFLARE
3899 else
3900 _debug "Use google doh server"
3901 export DOH_USE=$DOH_GOOGLE
3902 fi
3903 fi
3904
3905 if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
3906 _ns_lookup_cf "$@"
3907 else
3908 _ns_lookup_google "$@"
3909 fi
3910
3911 }
3912
3913 #txtdomain, alias, txt
3914 __check_txt() {
3915 _c_txtdomain="$1"
3916 _c_aliasdomain="$2"
3917 _c_txt="$3"
3918 _debug "_c_txtdomain" "$_c_txtdomain"
3919 _debug "_c_aliasdomain" "$_c_aliasdomain"
3920 _debug "_c_txt" "$_c_txt"
3921 _answers="$(_ns_lookup "$_c_aliasdomain" TXT)"
3922 _contains "$_answers" "$_c_txt"
3923
3924 }
3925
3926 #txtdomain
3927 __purge_txt() {
3928 _p_txtdomain="$1"
3929 _debug _p_txtdomain "$_p_txtdomain"
3930 if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
3931 _ns_purge_cf "$_p_txtdomain" "TXT"
3932 else
3933 _debug "no purge api for google dns api, just sleep 5 secs"
3934 _sleep 5
3935 fi
3936
3937 }
3938
3939 #wait and check each dns entries
3940 _check_dns_entries() {
3941 _success_txt=","
3942 _end_time="$(_time)"
3943 _end_time="$(_math "$_end_time" + 1200)" #let's check no more than 20 minutes.
3944
3945 while [ "$(_time)" -le "$_end_time" ]; do
3946 _left=""
3947 for entry in $dns_entries; do
3948 d=$(_getfield "$entry" 1)
3949 txtdomain=$(_getfield "$entry" 2)
3950 txtdomain=$(_idn "$txtdomain")
3951 aliasDomain=$(_getfield "$entry" 3)
3952 aliasDomain=$(_idn "$aliasDomain")
3953 txt=$(_getfield "$entry" 5)
3954 d_api=$(_getfield "$entry" 6)
3955 _debug "d" "$d"
3956 _debug "txtdomain" "$txtdomain"
3957 _debug "aliasDomain" "$aliasDomain"
3958 _debug "txt" "$txt"
3959 _debug "d_api" "$d_api"
3960 _info "Checking $d for $aliasDomain"
3961 if _contains "$_success_txt" ",$txt,"; then
3962 _info "Already success, continue next one."
3963 continue
3964 fi
3965
3966 if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then
3967 _info "Domain $d '$aliasDomain' success."
3968 _success_txt="$_success_txt,$txt,"
3969 continue
3970 fi
3971 _left=1
3972 _info "Not valid yet, let's wait 10 seconds and check next one."
3973 __purge_txt "$txtdomain"
3974 if [ "$txtdomain" != "$aliasDomain" ]; then
3975 __purge_txt "$aliasDomain"
3976 fi
3977 _sleep 10
3978 done
3979 if [ "$_left" ]; then
3980 _info "Let's wait 10 seconds and check again".
3981 _sleep 10
3982 else
3983 _info "All success, let's return"
3984 return 0
3985 fi
3986 done
3987 _info "Timed out waiting for DNS."
3988 return 1
3989
3990 }
3991
3992 #file
3993 _get_cert_issuers() {
3994 _cfile="$1"
3995 if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7"; then
3996 ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
3997 else
3998 ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
3999 fi
4000 }
4001
4002 #cert issuer
4003 _match_issuer() {
4004 _cfile="$1"
4005 _missuer="$2"
4006 _fissuers="$(_get_cert_issuers $_cfile)"
4007 _debug2 _fissuers "$_fissuers"
4008 _contains "$_fissuers" "$_missuer"
4009 }
4010
4011 #webroot, domain domainlist keylength
4012 issue() {
4013 if [ -z "$2" ]; then
4014 _usage "Usage: $PROJECT_ENTRY --issue -d a.com -w /path/to/webroot/a.com/ "
4015 return 1
4016 fi
4017 if [ -z "$1" ]; then
4018 _usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc."
4019 return 1
4020 fi
4021 _web_roots="$1"
4022 _main_domain="$2"
4023 _alt_domains="$3"
4024
4025 if _contains "$_main_domain" ","; then
4026 _main_domain=$(echo "$2,$3" | cut -d , -f 1)
4027 _alt_domains=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//")
4028 fi
4029 _debug _main_domain "$_main_domain"
4030 _debug _alt_domains "$_alt_domains"
4031
4032 _key_length="$4"
4033 _real_cert="$5"
4034 _real_key="$6"
4035 _real_ca="$7"
4036 _reload_cmd="$8"
4037 _real_fullchain="$9"
4038 _pre_hook="${10}"
4039 _post_hook="${11}"
4040 _renew_hook="${12}"
4041 _local_addr="${13}"
4042 _challenge_alias="${14}"
4043 _preferred_chain="${15}"
4044
4045 if [ -z "$_ACME_IS_RENEW" ]; then
4046 _initpath "$_main_domain" "$_key_length"
4047 mkdir -p "$DOMAIN_PATH"
4048 fi
4049
4050 if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
4051 _err "$_DNS_MANUAL_ERROR"
4052 return 1
4053 fi
4054
4055 _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
4056
4057 _initAPI
4058
4059 if [ -f "$DOMAIN_CONF" ]; then
4060 Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)
4061 _debug Le_NextRenewTime "$Le_NextRenewTime"
4062 if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
4063 _saved_domain=$(_readdomainconf Le_Domain)
4064 _debug _saved_domain "$_saved_domain"
4065 _saved_alt=$(_readdomainconf Le_Alt)
4066 _debug _saved_alt "$_saved_alt"
4067 if [ "$_saved_domain,$_saved_alt" = "$_main_domain,$_alt_domains" ]; then
4068 _info "Domains not changed."
4069 _info "Skip, Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")"
4070 _info "Add '$(__red '--force')' to force to renew."
4071 return $RENEW_SKIP
4072 else
4073 _info "Domains have changed."
4074 fi
4075 fi
4076 fi
4077
4078 _savedomainconf "Le_Domain" "$_main_domain"
4079 _savedomainconf "Le_Alt" "$_alt_domains"
4080 _savedomainconf "Le_Webroot" "$_web_roots"
4081
4082 _savedomainconf "Le_PreHook" "$_pre_hook" "base64"
4083 _savedomainconf "Le_PostHook" "$_post_hook" "base64"
4084 _savedomainconf "Le_RenewHook" "$_renew_hook" "base64"
4085
4086 if [ "$_local_addr" ]; then
4087 _savedomainconf "Le_LocalAddress" "$_local_addr"
4088 else
4089 _cleardomainconf "Le_LocalAddress"
4090 fi
4091 if [ "$_challenge_alias" ]; then
4092 _savedomainconf "Le_ChallengeAlias" "$_challenge_alias"
4093 else
4094 _cleardomainconf "Le_ChallengeAlias"
4095 fi
4096 if [ "$_preferred_chain" ]; then
4097 _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64"
4098 else
4099 _cleardomainconf "Le_Preferred_Chain"
4100 fi
4101
4102 Le_API="$ACME_DIRECTORY"
4103 _savedomainconf "Le_API" "$Le_API"
4104
4105 _info "Using CA: $ACME_DIRECTORY"
4106 if [ "$_alt_domains" = "$NO_VALUE" ]; then
4107 _alt_domains=""
4108 fi
4109
4110 if [ "$_key_length" = "$NO_VALUE" ]; then
4111 _key_length=""
4112 fi
4113
4114 if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then
4115 _err "_on_before_issue."
4116 return 1
4117 fi
4118
4119 _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")"
4120 _debug2 _saved_account_key_hash "$_saved_account_key_hash"
4121
4122 if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then
4123 if ! _regAccount "$_accountkeylength"; then
4124 _on_issue_err "$_post_hook"
4125 return 1
4126 fi
4127 else
4128 _debug "_saved_account_key_hash is not changed, skip register account."
4129 fi
4130
4131 if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then
4132 _info "Signing from existing CSR."
4133 else
4134 _key=$(_readdomainconf Le_Keylength)
4135 _debug "Read key length:$_key"
4136 if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then
4137 if ! createDomainKey "$_main_domain" "$_key_length"; then
4138 _err "Create domain key error."
4139 _clearup
4140 _on_issue_err "$_post_hook"
4141 return 1
4142 fi
4143 fi
4144
4145 if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then
4146 _err "Create CSR error."
4147 _clearup
4148 _on_issue_err "$_post_hook"
4149 return 1
4150 fi
4151 fi
4152
4153 _savedomainconf "Le_Keylength" "$_key_length"
4154
4155 vlist="$Le_Vlist"
4156 _cleardomainconf "Le_Vlist"
4157 _info "Getting domain auth token for each domain"
4158 sep='#'
4159 dvsep=','
4160 if [ -z "$vlist" ]; then
4161 if [ "$ACME_VERSION" = "2" ]; then
4162 #make new order request
4163 _identifiers="{\"type\":\"dns\",\"value\":\"$(_idn "$_main_domain")\"}"
4164 _w_index=1
4165 while true; do
4166 d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")"
4167 _w_index="$(_math "$_w_index" + 1)"
4168 _debug d "$d"
4169 if [ -z "$d" ]; then
4170 break
4171 fi
4172 _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$(_idn "$d")\"}"
4173 done
4174 _debug2 _identifiers "$_identifiers"
4175 if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
4176 _err "Create new order error."
4177 _clearup
4178 _on_issue_err "$_post_hook"
4179 return 1
4180 fi
4181 Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
4182 _debug Le_LinkOrder "$Le_LinkOrder"
4183 Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
4184 _debug Le_OrderFinalize "$Le_OrderFinalize"
4185 if [ -z "$Le_OrderFinalize" ]; then
4186 _err "Create new order error. Le_OrderFinalize not found. $response"
4187 _clearup
4188 _on_issue_err "$_post_hook"
4189 return 1
4190 fi
4191
4192 #for dns manual mode
4193 _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
4194
4195 _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
4196 _debug2 _authorizations_seg "$_authorizations_seg"
4197 if [ -z "$_authorizations_seg" ]; then
4198 _err "_authorizations_seg not found."
4199 _clearup
4200 _on_issue_err "$_post_hook"
4201 return 1
4202 fi
4203
4204 #domain and authz map
4205 _authorizations_map=""
4206 for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
4207 _debug2 "_authz_url" "$_authz_url"
4208 if ! _send_signed_request "$_authz_url"; then
4209 _err "get to authz error."
4210 _err "_authorizations_seg" "$_authorizations_seg"
4211 _err "_authz_url" "$_authz_url"
4212 _clearup
4213 _on_issue_err "$_post_hook"
4214 return 1
4215 fi
4216
4217 response="$(echo "$response" | _normalizeJson)"
4218 _debug2 response "$response"
4219 _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2 | tr -d ' "')"
4220 if _contains "$response" "\"wildcard\" *: *true"; then
4221 _d="*.$_d"
4222 fi
4223 _debug2 _d "$_d"
4224 _authorizations_map="$_d,$response
4225 $_authorizations_map"
4226 done
4227 _debug2 _authorizations_map "$_authorizations_map"
4228 fi
4229
4230 _index=0
4231 _currentRoot=""
4232 _w_index=1
4233 while true; do
4234 d="$(echo "$_main_domain,$_alt_domains," | cut -d , -f "$_w_index")"
4235 _w_index="$(_math "$_w_index" + 1)"
4236 _debug d "$d"
4237 if [ -z "$d" ]; then
4238 break
4239 fi
4240 _info "Getting webroot for domain" "$d"
4241 _index=$(_math $_index + 1)
4242 _w="$(echo $_web_roots | cut -d , -f $_index)"
4243 _debug _w "$_w"
4244 if [ "$_w" ]; then
4245 _currentRoot="$_w"
4246 fi
4247 _debug "_currentRoot" "$_currentRoot"
4248
4249 vtype="$VTYPE_HTTP"
4250 #todo, v2 wildcard force to use dns
4251 if _startswith "$_currentRoot" "$W_DNS"; then
4252 vtype="$VTYPE_DNS"
4253 fi
4254
4255 if [ "$_currentRoot" = "$W_ALPN" ]; then
4256 vtype="$VTYPE_ALPN"
4257 fi
4258
4259 if [ "$ACME_VERSION" = "2" ]; then
4260 _idn_d="$(_idn "$d")"
4261 _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
4262 _debug2 _candidates "$_candidates"
4263 if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then
4264 for _can in $_candidates; do
4265 if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
4266 _candidates="$_can"
4267 break
4268 fi
4269 done
4270 fi
4271 response="$(echo "$_candidates" | sed "s/$_idn_d,//")"
4272 _debug2 "response" "$response"
4273 if [ -z "$response" ]; then
4274 _err "get to authz error."
4275 _err "_authorizations_map" "$_authorizations_map"
4276 _clearup
4277 _on_issue_err "$_post_hook"
4278 return 1
4279 fi
4280 else
4281 if ! __get_domain_new_authz "$d"; then
4282 _clearup
4283 _on_issue_err "$_post_hook"
4284 return 1
4285 fi
4286 fi
4287
4288 if [ -z "$thumbprint" ]; then
4289 thumbprint="$(__calc_account_thumbprint)"
4290 fi
4291
4292 entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
4293 _debug entry "$entry"
4294 keyauthorization=""
4295 if [ -z "$entry" ]; then
4296 if ! _startswith "$d" '*.'; then
4297 _debug "Not a wildcard domain, lets check whether the validation is already valid."
4298 if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
4299 _debug "$d is already valid."
4300 keyauthorization="$STATE_VERIFIED"
4301 _debug keyauthorization "$keyauthorization"
4302 fi
4303 fi
4304 if [ -z "$keyauthorization" ]; then
4305 _err "Error, can not get domain token entry $d for $vtype"
4306 _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
4307 if [ "$_supported_vtypes" ]; then
4308 _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
4309 fi
4310 _clearup
4311 _on_issue_err "$_post_hook"
4312 return 1
4313 fi
4314 fi
4315
4316 if [ -z "$keyauthorization" ]; then
4317 token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
4318 _debug token "$token"
4319
4320 if [ -z "$token" ]; then
4321 _err "Error, can not get domain token $entry"
4322 _clearup
4323 _on_issue_err "$_post_hook"
4324 return 1
4325 fi
4326 if [ "$ACME_VERSION" = "2" ]; then
4327 uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
4328 else
4329 uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
4330 fi
4331 _debug uri "$uri"
4332
4333 if [ -z "$uri" ]; then
4334 _err "Error, can not get domain uri. $entry"
4335 _clearup
4336 _on_issue_err "$_post_hook"
4337 return 1
4338 fi
4339 keyauthorization="$token.$thumbprint"
4340 _debug keyauthorization "$keyauthorization"
4341
4342 if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
4343 _debug "$d is already verified."
4344 keyauthorization="$STATE_VERIFIED"
4345 _debug keyauthorization "$keyauthorization"
4346 fi
4347 fi
4348
4349 dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
4350 _debug dvlist "$dvlist"
4351
4352 vlist="$vlist$dvlist$dvsep"
4353
4354 done
4355 _debug vlist "$vlist"
4356 #add entry
4357 dns_entries=""
4358 dnsadded=""
4359 ventries=$(echo "$vlist" | tr "$dvsep" ' ')
4360 _alias_index=1
4361 for ventry in $ventries; do
4362 d=$(echo "$ventry" | cut -d "$sep" -f 1)
4363 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
4364 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
4365 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
4366 _debug d "$d"
4367 if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
4368 _debug "$d is already verified, skip $vtype."
4369 _alias_index="$(_math "$_alias_index" + 1)"
4370 continue
4371 fi
4372
4373 if [ "$vtype" = "$VTYPE_DNS" ]; then
4374 dnsadded='0'
4375 _dns_root_d="$d"
4376 if _startswith "$_dns_root_d" "*."; then
4377 _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')"
4378 fi
4379 _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")"
4380 _alias_index="$(_math "$_alias_index" + 1)"
4381 _debug "_d_alias" "$_d_alias"
4382 if [ "$_d_alias" ]; then
4383 if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then
4384 txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")"
4385 else
4386 txtdomain="_acme-challenge.$_d_alias"
4387 fi
4388 dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$txtdomain$dvsep$_currentRoot"
4389 else
4390 txtdomain="_acme-challenge.$_dns_root_d"
4391 dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$dvsep$_currentRoot"
4392 fi
4393
4394 _debug txtdomain "$txtdomain"
4395 txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)"
4396 _debug txt "$txt"
4397
4398 d_api="$(_findHook "$_dns_root_d" $_SUB_FOLDER_DNSAPI "$_currentRoot")"
4399 _debug d_api "$d_api"
4400
4401 dns_entry="$dns_entry$dvsep$txt${dvsep}$d_api"
4402 _debug2 dns_entry "$dns_entry"
4403 if [ "$d_api" ]; then
4404 _debug "Found domain api file: $d_api"
4405 else
4406 if [ "$_currentRoot" != "$W_DNS" ]; then
4407 _err "Can not find dns api hook for: $_currentRoot"
4408 _info "You need to add the txt record manually."
4409 fi
4410 _info "$(__red "Add the following TXT record:")"
4411 _info "$(__red "Domain: '$(__green "$txtdomain")'")"
4412 _info "$(__red "TXT value: '$(__green "$txt")'")"
4413 _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")"
4414 _info "$(__red "so the resulting subdomain will be: $txtdomain")"
4415 continue
4416 fi
4417
4418 (
4419 if ! . "$d_api"; then
4420 _err "Load file $d_api error. Please check your api file and try again."
4421 return 1
4422 fi
4423
4424 addcommand="${_currentRoot}_add"
4425 if ! _exists "$addcommand"; then
4426 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
4427 return 1
4428 fi
4429 _info "Adding txt value: $txt for domain: $txtdomain"
4430 if ! $addcommand "$txtdomain" "$txt"; then
4431 _err "Error add txt for domain:$txtdomain"
4432 return 1
4433 fi
4434 _info "The txt record is added: Success."
4435 )
4436
4437 if [ "$?" != "0" ]; then
4438 _on_issue_err "$_post_hook" "$vlist"
4439 _clearup
4440 return 1
4441 fi
4442 dns_entries="$dns_entries$dns_entry
4443 "
4444 _debug2 "$dns_entries"
4445 dnsadded='1'
4446 fi
4447 done
4448
4449 if [ "$dnsadded" = '0' ]; then
4450 _savedomainconf "Le_Vlist" "$vlist"
4451 _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
4452 _err "Please add the TXT records to the domains, and re-run with --renew."
4453 _on_issue_err "$_post_hook"
4454 _clearup
4455 return 1
4456 fi
4457
4458 fi
4459
4460 if [ "$dns_entries" ]; then
4461 if [ -z "$Le_DNSSleep" ]; then
4462 _info "Let's check each DNS record now. Sleep 20 seconds first."
4463 _sleep 20
4464 if ! _check_dns_entries; then
4465 _err "check dns error."
4466 _on_issue_err "$_post_hook"
4467 _clearup
4468 return 1
4469 fi
4470 else
4471 _savedomainconf "Le_DNSSleep" "$Le_DNSSleep"
4472 _info "Sleep $(__green $Le_DNSSleep) seconds for the txt records to take effect"
4473 _sleep "$Le_DNSSleep"
4474 fi
4475 fi
4476
4477 NGINX_RESTORE_VLIST=""
4478 _debug "ok, let's start to verify"
4479
4480 _ncIndex=1
4481 ventries=$(echo "$vlist" | tr "$dvsep" ' ')
4482 for ventry in $ventries; do
4483 d=$(echo "$ventry" | cut -d "$sep" -f 1)
4484 keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2)
4485 uri=$(echo "$ventry" | cut -d "$sep" -f 3)
4486 vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
4487 _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
4488
4489 if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then
4490 _info "$d is already verified, skip $vtype."
4491 continue
4492 fi
4493
4494 _info "Verifying: $d"
4495 _debug "d" "$d"
4496 _debug "keyauthorization" "$keyauthorization"
4497 _debug "uri" "$uri"
4498 removelevel=""
4499 token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)"
4500
4501 _debug "_currentRoot" "$_currentRoot"
4502
4503 if [ "$vtype" = "$VTYPE_HTTP" ]; then
4504 if [ "$_currentRoot" = "$NO_VALUE" ]; then
4505 _info "Standalone mode server"
4506 _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
4507 _ncIndex="$(_math $_ncIndex + 1)"
4508 _startserver "$keyauthorization" "$_ncaddr"
4509 if [ "$?" != "0" ]; then
4510 _clearup
4511 _on_issue_err "$_post_hook" "$vlist"
4512 return 1
4513 fi
4514 sleep 1
4515 _debug serverproc "$serverproc"
4516 elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then
4517 _info "Stateless mode for domain:$d"
4518 _sleep 1
4519 elif _startswith "$_currentRoot" "$NGINX"; then
4520 _info "Nginx mode for domain:$d"
4521 #set up nginx server
4522 FOUND_REAL_NGINX_CONF=""
4523 BACKUP_NGINX_CONF=""
4524 if ! _setNginx "$d" "$_currentRoot" "$thumbprint"; then
4525 _clearup
4526 _on_issue_err "$_post_hook" "$vlist"
4527 return 1
4528 fi
4529
4530 if [ "$FOUND_REAL_NGINX_CONF" ]; then
4531 _realConf="$FOUND_REAL_NGINX_CONF"
4532 _backup="$BACKUP_NGINX_CONF"
4533 _debug _realConf "$_realConf"
4534 NGINX_RESTORE_VLIST="$d$sep$_realConf$sep$_backup$dvsep$NGINX_RESTORE_VLIST"
4535 fi
4536 _sleep 1
4537 else
4538 if [ "$_currentRoot" = "apache" ]; then
4539 wellknown_path="$ACME_DIR"
4540 else
4541 wellknown_path="$_currentRoot/.well-known/acme-challenge"
4542 if [ ! -d "$_currentRoot/.well-known" ]; then
4543 removelevel='1'
4544 elif [ ! -d "$_currentRoot/.well-known/acme-challenge" ]; then
4545 removelevel='2'
4546 else
4547 removelevel='3'
4548 fi
4549 fi
4550
4551 _debug wellknown_path "$wellknown_path"
4552
4553 _debug "writing token:$token to $wellknown_path/$token"
4554
4555 mkdir -p "$wellknown_path"
4556
4557 if ! printf "%s" "$keyauthorization" >"$wellknown_path/$token"; then
4558 _err "$d:Can not write token to file : $wellknown_path/$token"
4559 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4560 _clearup
4561 _on_issue_err "$_post_hook" "$vlist"
4562 return 1
4563 fi
4564
4565 if [ ! "$usingApache" ]; then
4566 if webroot_owner=$(_stat "$_currentRoot"); then
4567 _debug "Changing owner/group of .well-known to $webroot_owner"
4568 if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then
4569 _debug "$(cat "$_EXEC_TEMP_ERR")"
4570 _exec_err >/dev/null 2>&1
4571 fi
4572 else
4573 _debug "not changing owner/group of webroot"
4574 fi
4575 fi
4576
4577 fi
4578 elif [ "$vtype" = "$VTYPE_ALPN" ]; then
4579 acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")"
4580 _debug acmevalidationv1 "$acmevalidationv1"
4581 if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then
4582 _err "Start tls server error."
4583 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4584 _clearup
4585 _on_issue_err "$_post_hook" "$vlist"
4586 return 1
4587 fi
4588 fi
4589
4590 if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then
4591 _err "$d:Can not get challenge: $response"
4592 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4593 _clearup
4594 _on_issue_err "$_post_hook" "$vlist"
4595 return 1
4596 fi
4597
4598 if [ "$code" ] && [ "$code" != '202' ]; then
4599 if [ "$code" = '200' ]; then
4600 _debug "trigger validation code: $code"
4601 else
4602 _err "$d:Challenge error: $response"
4603 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4604 _clearup
4605 _on_issue_err "$_post_hook" "$vlist"
4606 return 1
4607 fi
4608 fi
4609
4610 waittimes=0
4611 if [ -z "$MAX_RETRY_TIMES" ]; then
4612 MAX_RETRY_TIMES=30
4613 fi
4614
4615 while true; do
4616 waittimes=$(_math "$waittimes" + 1)
4617 if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then
4618 _err "$d:Timeout"
4619 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4620 _clearup
4621 _on_issue_err "$_post_hook" "$vlist"
4622 return 1
4623 fi
4624
4625 _debug "sleep 2 secs to verify"
4626 sleep 2
4627 _debug "checking"
4628 if [ "$ACME_VERSION" = "2" ]; then
4629 _send_signed_request "$uri"
4630 else
4631 response="$(_get "$uri")"
4632 fi
4633 if [ "$?" != "0" ]; then
4634 _err "$d:Verify error:$response"
4635 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4636 _clearup
4637 _on_issue_err "$_post_hook" "$vlist"
4638 return 1
4639 fi
4640 _debug2 original "$response"
4641
4642 response="$(echo "$response" | _normalizeJson)"
4643 _debug2 response "$response"
4644
4645 status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
4646 if [ "$status" = "valid" ]; then
4647 _info "$(__green Success)"
4648 _stopserver "$serverproc"
4649 serverproc=""
4650 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4651 break
4652 fi
4653
4654 if [ "$status" = "invalid" ]; then
4655 error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
4656 _debug2 error "$error"
4657 errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
4658 _debug2 errordetail "$errordetail"
4659 if [ "$errordetail" ]; then
4660 _err "$d:Verify error:$errordetail"
4661 else
4662 _err "$d:Verify error:$error"
4663 fi
4664 if [ "$DEBUG" ]; then
4665 if [ "$vtype" = "$VTYPE_HTTP" ]; then
4666 _debug "Debug: get token url."
4667 _get "http://$d/.well-known/acme-challenge/$token" "" 1
4668 fi
4669 fi
4670 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4671 _clearup
4672 _on_issue_err "$_post_hook" "$vlist"
4673 return 1
4674 fi
4675
4676 if [ "$status" = "pending" ]; then
4677 _info "Pending"
4678 elif [ "$status" = "processing" ]; then
4679 _info "Processing"
4680 else
4681 _err "$d:Verify error:$response"
4682 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4683 _clearup
4684 _on_issue_err "$_post_hook" "$vlist"
4685 return 1
4686 fi
4687
4688 done
4689
4690 done
4691
4692 _clearup
4693 _info "Verify finished, start to sign."
4694 der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
4695
4696 if [ "$ACME_VERSION" = "2" ]; then
4697 _info "Lets finalize the order."
4698 _info "Le_OrderFinalize" "$Le_OrderFinalize"
4699 if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
4700 _err "Sign failed."
4701 _on_issue_err "$_post_hook"
4702 return 1
4703 fi
4704 if [ "$code" != "200" ]; then
4705 _err "Sign failed, finalize code is not 200."
4706 _err "$response"
4707 _on_issue_err "$_post_hook"
4708 return 1
4709 fi
4710 if [ -z "$Le_LinkOrder" ]; then
4711 Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d ":" -f 2-)"
4712 fi
4713
4714 _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
4715
4716 _link_cert_retry=0
4717 _MAX_CERT_RETRY=30
4718 while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
4719 if _contains "$response" "\"status\":\"valid\""; then
4720 _debug "Order status is valid."
4721 Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
4722 _debug Le_LinkCert "$Le_LinkCert"
4723 if [ -z "$Le_LinkCert" ]; then
4724 _err "Sign error, can not find Le_LinkCert"
4725 _err "$response"
4726 _on_issue_err "$_post_hook"
4727 return 1
4728 fi
4729 break
4730 elif _contains "$response" "\"processing\""; then
4731 _info "Order status is processing, lets sleep and retry."
4732 _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
4733 _debug "_retryafter" "$_retryafter"
4734 if [ "$_retryafter" ]; then
4735 _info "Retry after: $_retryafter"
4736 _sleep $_retryafter
4737 else
4738 _sleep 2
4739 fi
4740 else
4741 _err "Sign error, wrong status"
4742 _err "$response"
4743 _on_issue_err "$_post_hook"
4744 return 1
4745 fi
4746 #the order is processing, so we are going to poll order status
4747 if [ -z "$Le_LinkOrder" ]; then
4748 _err "Sign error, can not get order link location header"
4749 _err "responseHeaders" "$responseHeaders"
4750 _on_issue_err "$_post_hook"
4751 return 1
4752 fi
4753 _info "Polling order status: $Le_LinkOrder"
4754 if ! _send_signed_request "$Le_LinkOrder"; then
4755 _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
4756 _err "$response"
4757 _on_issue_err "$_post_hook"
4758 return 1
4759 fi
4760 _link_cert_retry="$(_math $_link_cert_retry + 1)"
4761 done
4762
4763 if [ -z "$Le_LinkCert" ]; then
4764 _err "Sign failed, can not get Le_LinkCert, retry time limit."
4765 _err "$response"
4766 _on_issue_err "$_post_hook"
4767 return 1
4768 fi
4769 _info "Downloading cert."
4770 _info "Le_LinkCert" "$Le_LinkCert"
4771 if ! _send_signed_request "$Le_LinkCert"; then
4772 _err "Sign failed, can not download cert:$Le_LinkCert."
4773 _err "$response"
4774 _on_issue_err "$_post_hook"
4775 return 1
4776 fi
4777
4778 echo "$response" >"$CERT_PATH"
4779 _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
4780
4781 if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then
4782 if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then
4783 rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)"
4784 _debug2 "rels" "$rels"
4785 for rel in $rels; do
4786 _info "Try rel: $rel"
4787 if ! _send_signed_request "$rel"; then
4788 _err "Sign failed, can not download cert:$rel"
4789 _err "$response"
4790 continue
4791 fi
4792 _relcert="$CERT_PATH.alt"
4793 _relfullchain="$CERT_FULLCHAIN_PATH.alt"
4794 _relca="$CA_CERT_PATH.alt"
4795 echo "$response" >"$_relcert"
4796 _split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
4797 if _match_issuer "$_relfullchain" "$_preferred_chain"; then
4798 _info "Matched issuer in: $rel"
4799 cat $_relcert >"$CERT_PATH"
4800 cat $_relfullchain >"$CERT_FULLCHAIN_PATH"
4801 cat $_relca >"$CA_CERT_PATH"
4802 break
4803 fi
4804 done
4805 fi
4806 fi
4807 else
4808 if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
4809 _err "Sign failed. $response"
4810 _on_issue_err "$_post_hook"
4811 return 1
4812 fi
4813 _rcert="$response"
4814 Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
4815 echo "$BEGIN_CERT" >"$CERT_PATH"
4816
4817 #if ! _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH" ; then
4818 # _debug "Get cert failed. Let's try last response."
4819 # printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH"
4820 #fi
4821
4822 if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
4823 _debug "Try cert link."
4824 _get "$Le_LinkCert" | _base64 "multiline" >>"$CERT_PATH"
4825 fi
4826
4827 echo "$END_CERT" >>"$CERT_PATH"
4828 fi
4829
4830 _debug "Le_LinkCert" "$Le_LinkCert"
4831 _savedomainconf "Le_LinkCert" "$Le_LinkCert"
4832
4833 if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then
4834 response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)"
4835 _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')"
4836 _on_issue_err "$_post_hook"
4837 return 1
4838 fi
4839
4840 if [ "$Le_LinkCert" ]; then
4841 _info "$(__green "Cert success.")"
4842 cat "$CERT_PATH"
4843
4844 _info "Your cert is in $(__green " $CERT_PATH ")"
4845
4846 if [ -f "$CERT_KEY_PATH" ]; then
4847 _info "Your cert key is in $(__green " $CERT_KEY_PATH ")"
4848 fi
4849
4850 if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then
4851 USER_PATH="$PATH"
4852 _saveaccountconf "USER_PATH" "$USER_PATH"
4853 fi
4854 fi
4855
4856 if [ "$ACME_VERSION" = "2" ]; then
4857 _debug "v2 chain."
4858 else
4859 cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
4860 Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>')
4861
4862 if [ "$Le_LinkIssuer" ]; then
4863 if ! _contains "$Le_LinkIssuer" ":"; then
4864 _info "$(__red "Relative issuer link found.")"
4865 Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer"
4866 fi
4867 _debug Le_LinkIssuer "$Le_LinkIssuer"
4868 _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
4869
4870 _link_issuer_retry=0
4871 _MAX_ISSUER_RETRY=5
4872 while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
4873 _debug _link_issuer_retry "$_link_issuer_retry"
4874 if [ "$ACME_VERSION" = "2" ]; then
4875 if _send_signed_request "$Le_LinkIssuer"; then
4876 echo "$response" >"$CA_CERT_PATH"
4877 break
4878 fi
4879 else
4880 if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then
4881 echo "$BEGIN_CERT" >"$CA_CERT_PATH"
4882 _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH"
4883 echo "$END_CERT" >>"$CA_CERT_PATH"
4884 if ! _checkcert "$CA_CERT_PATH"; then
4885 _err "Can not get the ca cert."
4886 break
4887 fi
4888 cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
4889 rm -f "$CA_CERT_PATH.der"
4890 break
4891 fi
4892 fi
4893 _link_issuer_retry=$(_math $_link_issuer_retry + 1)
4894 _sleep "$_link_issuer_retry"
4895 done
4896 if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then
4897 _err "Max retry for issuer ca cert is reached."
4898 fi
4899 else
4900 _debug "No Le_LinkIssuer header found."
4901 fi
4902 fi
4903 [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")"
4904 [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
4905
4906 Le_CertCreateTime=$(_time)
4907 _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
4908
4909 Le_CertCreateTimeStr=$(date -u)
4910 _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr"
4911
4912 if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then
4913 Le_RenewalDays="$DEFAULT_RENEW"
4914 else
4915 _savedomainconf "Le_RenewalDays" "$Le_RenewalDays"
4916 fi
4917
4918 if [ "$CA_BUNDLE" ]; then
4919 _saveaccountconf CA_BUNDLE "$CA_BUNDLE"
4920 else
4921 _clearaccountconf "CA_BUNDLE"
4922 fi
4923
4924 if [ "$CA_PATH" ]; then
4925 _saveaccountconf CA_PATH "$CA_PATH"
4926 else
4927 _clearaccountconf "CA_PATH"
4928 fi
4929
4930 if [ "$HTTPS_INSECURE" ]; then
4931 _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
4932 else
4933 _clearaccountconf "HTTPS_INSECURE"
4934 fi
4935
4936 if [ "$Le_Listen_V4" ]; then
4937 _savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
4938 _cleardomainconf Le_Listen_V6
4939 elif [ "$Le_Listen_V6" ]; then
4940 _savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
4941 _cleardomainconf Le_Listen_V4
4942 fi
4943
4944 if [ "$Le_ForceNewDomainKey" = "1" ]; then
4945 _savedomainconf "Le_ForceNewDomainKey" "$Le_ForceNewDomainKey"
4946 else
4947 _cleardomainconf Le_ForceNewDomainKey
4948 fi
4949
4950 Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
4951
4952 Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
4953 _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
4954
4955 Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
4956 _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
4957
4958 if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
4959 _savedomainconf "Le_RealCertPath" "$_real_cert"
4960 _savedomainconf "Le_RealCACertPath" "$_real_ca"
4961 _savedomainconf "Le_RealKeyPath" "$_real_key"
4962 _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
4963 _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
4964 if ! _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
4965 return 1
4966 fi
4967 fi
4968
4969 if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
4970 _err "Call hook error."
4971 return 1
4972 fi
4973 }
4974
4975 #in_out_cert out_fullchain out out_ca
4976 _split_cert_chain() {
4977 _certf="$1"
4978 _fullchainf="$2"
4979 _caf="$3"
4980 if [ "$(grep -- "$BEGIN_CERT" "$_certf" | wc -l)" -gt "1" ]; then
4981 _debug "Found cert chain"
4982 cat "$_certf" >"$_fullchainf"
4983 _end_n="$(grep -n -- "$END_CERT" "$_fullchainf" | _head_n 1 | cut -d : -f 1)"
4984 _debug _end_n "$_end_n"
4985 sed -n "1,${_end_n}p" "$_fullchainf" >"$_certf"
4986 _end_n="$(_math $_end_n + 1)"
4987 sed -n "${_end_n},9999p" "$_fullchainf" >"$_caf"
4988 fi
4989 }
4990
4991 #domain [isEcc]
4992 renew() {
4993 Le_Domain="$1"
4994 if [ -z "$Le_Domain" ]; then
4995 _usage "Usage: $PROJECT_ENTRY --renew -d domain.com [--ecc]"
4996 return 1
4997 fi
4998
4999 _isEcc="$2"
5000
5001 _initpath "$Le_Domain" "$_isEcc"
5002
5003 _info "$(__green "Renew: '$Le_Domain'")"
5004 if [ ! -f "$DOMAIN_CONF" ]; then
5005 _info "'$Le_Domain' is not a issued domain, skip."
5006 return $RENEW_SKIP
5007 fi
5008
5009 if [ "$Le_RenewalDays" ]; then
5010 _savedomainconf Le_RenewalDays "$Le_RenewalDays"
5011 fi
5012
5013 . "$DOMAIN_CONF"
5014 _debug Le_API "$Le_API"
5015
5016 if [ "$Le_API" = "$LETSENCRYPT_CA_V1" ]; then
5017 _cleardomainconf Le_API
5018 Le_API="$DEFAULT_CA"
5019 fi
5020 if [ "$Le_API" = "$LETSENCRYPT_STAGING_CA_V1" ]; then
5021 _cleardomainconf Le_API
5022 Le_API="$DEFAULT_STAGING_CA"
5023 fi
5024
5025 if [ "$Le_API" ]; then
5026 export ACME_DIRECTORY="$Le_API"
5027 #reload ca configs
5028 ACCOUNT_KEY_PATH=""
5029 ACCOUNT_JSON_PATH=""
5030 CA_CONF=""
5031 _debug3 "initpath again."
5032 _initpath "$Le_Domain" "$_isEcc"
5033 fi
5034
5035 if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then
5036 _info "Skip, Next renewal time is: $(__green "$Le_NextRenewTimeStr")"
5037 _info "Add '$(__red '--force')' to force to renew."
5038 return "$RENEW_SKIP"
5039 fi
5040
5041 if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then
5042 _info "Skip invalid cert for: $Le_Domain"
5043 return $RENEW_SKIP
5044 fi
5045
5046 _ACME_IS_RENEW="1"
5047 Le_ReloadCmd="$(_readdomainconf Le_ReloadCmd)"
5048 Le_PreHook="$(_readdomainconf Le_PreHook)"
5049 Le_PostHook="$(_readdomainconf Le_PostHook)"
5050 Le_RenewHook="$(_readdomainconf Le_RenewHook)"
5051 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"
5052 res="$?"
5053 if [ "$res" != "0" ]; then
5054 return "$res"
5055 fi
5056
5057 if [ "$Le_DeployHook" ]; then
5058 _deploy "$Le_Domain" "$Le_DeployHook"
5059 res="$?"
5060 fi
5061
5062 _ACME_IS_RENEW=""
5063
5064 return "$res"
5065 }
5066
5067 #renewAll [stopRenewOnError]
5068 renewAll() {
5069 _initpath
5070 _stopRenewOnError="$1"
5071 _debug "_stopRenewOnError" "$_stopRenewOnError"
5072 _ret="0"
5073 _success_msg=""
5074 _error_msg=""
5075 _skipped_msg=""
5076 _error_level=$NOTIFY_LEVEL_SKIP
5077 _notify_code=$RENEW_SKIP
5078 _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT}
5079 _debug "_set_level" "$_set_level"
5080 for di in "${CERT_HOME}"/*.*/; do
5081 _debug di "$di"
5082 if ! [ -d "$di" ]; then
5083 _debug "Not directory, skip: $di"
5084 continue
5085 fi
5086 d=$(basename "$di")
5087 _debug d "$d"
5088 (
5089 if _endswith "$d" "$ECC_SUFFIX"; then
5090 _isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2)
5091 d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
5092 fi
5093 renew "$d" "$_isEcc"
5094 )
5095 rc="$?"
5096 _debug "Return code: $rc"
5097 if [ "$rc" = "0" ]; then
5098 if [ $_error_level -gt $NOTIFY_LEVEL_RENEW ]; then
5099 _error_level="$NOTIFY_LEVEL_RENEW"
5100 _notify_code=0
5101 fi
5102 if [ "$_ACME_IN_CRON" ]; then
5103 if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then
5104 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5105 _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0
5106 fi
5107 fi
5108 fi
5109 _success_msg="${_success_msg} $d
5110 "
5111 elif [ "$rc" = "$RENEW_SKIP" ]; then
5112 if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then
5113 _error_level="$NOTIFY_LEVEL_SKIP"
5114 _notify_code=$RENEW_SKIP
5115 fi
5116 if [ "$_ACME_IN_CRON" ]; then
5117 if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then
5118 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5119 _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP"
5120 fi
5121 fi
5122 fi
5123 _info "Skipped $d"
5124 _skipped_msg="${_skipped_msg} $d
5125 "
5126 else
5127 if [ $_error_level -gt $NOTIFY_LEVEL_ERROR ]; then
5128 _error_level="$NOTIFY_LEVEL_ERROR"
5129 _notify_code=1
5130 fi
5131 if [ "$_ACME_IN_CRON" ]; then
5132 if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then
5133 if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then
5134 _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1
5135 fi
5136 fi
5137 fi
5138 _error_msg="${_error_msg} $d
5139 "
5140 if [ "$_stopRenewOnError" ]; then
5141 _err "Error renew $d, stop now."
5142 _ret="$rc"
5143 break
5144 else
5145 _ret="$rc"
5146 _err "Error renew $d."
5147 fi
5148 fi
5149 done
5150 _debug _error_level "$_error_level"
5151 _debug _set_level "$_set_level"
5152 if [ "$_ACME_IN_CRON" ] && [ $_error_level -le $_set_level ]; then
5153 if [ -z "$NOTIFY_MODE" ] || [ "$NOTIFY_MODE" = "$NOTIFY_MODE_BULK" ]; then
5154 _msg_subject="Renew"
5155 if [ "$_error_msg" ]; then
5156 _msg_subject="${_msg_subject} Error"
5157 _msg_data="Error certs:
5158 ${_error_msg}
5159 "
5160 fi
5161 if [ "$_success_msg" ]; then
5162 _msg_subject="${_msg_subject} Success"
5163 _msg_data="${_msg_data}Success certs:
5164 ${_success_msg}
5165 "
5166 fi
5167 if [ "$_skipped_msg" ]; then
5168 _msg_subject="${_msg_subject} Skipped"
5169 _msg_data="${_msg_data}Skipped certs:
5170 ${_skipped_msg}
5171 "
5172 fi
5173
5174 _send_notify "$_msg_subject" "$_msg_data" "$NOTIFY_HOOK" "$_notify_code"
5175 fi
5176 fi
5177
5178 return "$_ret"
5179 }
5180
5181 #csr webroot
5182 signcsr() {
5183 _csrfile="$1"
5184 _csrW="$2"
5185 if [ -z "$_csrfile" ] || [ -z "$_csrW" ]; then
5186 _usage "Usage: $PROJECT_ENTRY --signcsr --csr mycsr.csr -w /path/to/webroot/a.com/ "
5187 return 1
5188 fi
5189
5190 _real_cert="$3"
5191 _real_key="$4"
5192 _real_ca="$5"
5193 _reload_cmd="$6"
5194 _real_fullchain="$7"
5195 _pre_hook="${8}"
5196 _post_hook="${9}"
5197 _renew_hook="${10}"
5198 _local_addr="${11}"
5199 _challenge_alias="${12}"
5200
5201 _csrsubj=$(_readSubjectFromCSR "$_csrfile")
5202 if [ "$?" != "0" ]; then
5203 _err "Can not read subject from csr: $_csrfile"
5204 return 1
5205 fi
5206 _debug _csrsubj "$_csrsubj"
5207 if _contains "$_csrsubj" ' ' || ! _contains "$_csrsubj" '.'; then
5208 _info "It seems that the subject: $_csrsubj is not a valid domain name. Drop it."
5209 _csrsubj=""
5210 fi
5211
5212 _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile")
5213 if [ "$?" != "0" ]; then
5214 _err "Can not read domain list from csr: $_csrfile"
5215 return 1
5216 fi
5217 _debug "_csrdomainlist" "$_csrdomainlist"
5218
5219 if [ -z "$_csrsubj" ]; then
5220 _csrsubj="$(_getfield "$_csrdomainlist" 1)"
5221 _debug _csrsubj "$_csrsubj"
5222 _csrdomainlist="$(echo "$_csrdomainlist" | cut -d , -f 2-)"
5223 _debug "_csrdomainlist" "$_csrdomainlist"
5224 fi
5225
5226 if [ -z "$_csrsubj" ]; then
5227 _err "Can not read subject from csr: $_csrfile"
5228 return 1
5229 fi
5230
5231 _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
5232 if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
5233 _err "Can not read key length from csr: $_csrfile"
5234 return 1
5235 fi
5236
5237 if [ -z "$ACME_VERSION" ] && _contains "$_csrsubj,$_csrdomainlist" "*."; then
5238 export ACME_VERSION=2
5239 fi
5240 _initpath "$_csrsubj" "$_csrkeylength"
5241 mkdir -p "$DOMAIN_PATH"
5242
5243 _info "Copy csr to: $CSR_PATH"
5244 cp "$_csrfile" "$CSR_PATH"
5245
5246 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"
5247
5248 }
5249
5250 showcsr() {
5251 _csrfile="$1"
5252 _csrd="$2"
5253 if [ -z "$_csrfile" ] && [ -z "$_csrd" ]; then
5254 _usage "Usage: $PROJECT_ENTRY --showcsr --csr mycsr.csr"
5255 return 1
5256 fi
5257
5258 _initpath
5259
5260 _csrsubj=$(_readSubjectFromCSR "$_csrfile")
5261 if [ "$?" != "0" ] || [ -z "$_csrsubj" ]; then
5262 _err "Can not read subject from csr: $_csrfile"
5263 return 1
5264 fi
5265
5266 _info "Subject=$_csrsubj"
5267
5268 _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile")
5269 if [ "$?" != "0" ]; then
5270 _err "Can not read domain list from csr: $_csrfile"
5271 return 1
5272 fi
5273 _debug "_csrdomainlist" "$_csrdomainlist"
5274
5275 _info "SubjectAltNames=$_csrdomainlist"
5276
5277 _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile")
5278 if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then
5279 _err "Can not read key length from csr: $_csrfile"
5280 return 1
5281 fi
5282 _info "KeyLength=$_csrkeylength"
5283 }
5284
5285 #listraw domain
5286 list() {
5287 _raw="$1"
5288 _domain="$2"
5289 _initpath
5290
5291 _sep="|"
5292 if [ "$_raw" ]; then
5293 if [ -z "$_domain" ]; then
5294 printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew"
5295 fi
5296 for di in "${CERT_HOME}"/*.*/; do
5297 d=$(basename "$di")
5298 _debug d "$d"
5299 (
5300 if _endswith "$d" "$ECC_SUFFIX"; then
5301 _isEcc="ecc"
5302 d=$(echo "$d" | cut -d "$ECC_SEP" -f 1)
5303 fi
5304 DOMAIN_CONF="$di/$d.conf"
5305 if [ -f "$DOMAIN_CONF" ]; then
5306 . "$DOMAIN_CONF"
5307 _ca="$(_getCAShortName "$Le_API")"
5308 if [ -z "$_domain" ]; then
5309 printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr"
5310 else
5311 if [ "$_domain" = "$d" ]; then
5312 cat "$DOMAIN_CONF"
5313 fi
5314 fi
5315 fi
5316 )
5317 done
5318 else
5319 if _exists column; then
5320 list "raw" "$_domain" | column -t -s "$_sep"
5321 else
5322 list "raw" "$_domain" | tr "$_sep" '\t'
5323 fi
5324 fi
5325
5326 }
5327
5328 _deploy() {
5329 _d="$1"
5330 _hooks="$2"
5331
5332 for _d_api in $(echo "$_hooks" | tr ',' " "); do
5333 _deployApi="$(_findHook "$_d" $_SUB_FOLDER_DEPLOY "$_d_api")"
5334 if [ -z "$_deployApi" ]; then
5335 _err "The deploy hook $_d_api is not found."
5336 return 1
5337 fi
5338 _debug _deployApi "$_deployApi"
5339
5340 if ! (
5341 if ! . "$_deployApi"; then
5342 _err "Load file $_deployApi error. Please check your api file and try again."
5343 return 1
5344 fi
5345
5346 d_command="${_d_api}_deploy"
5347 if ! _exists "$d_command"; then
5348 _err "It seems that your api file is not correct, it must have a function named: $d_command"
5349 return 1
5350 fi
5351
5352 if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH"; then
5353 _err "Error deploy for domain:$_d"
5354 return 1
5355 fi
5356 ); then
5357 _err "Deploy error."
5358 return 1
5359 else
5360 _info "$(__green Success)"
5361 fi
5362 done
5363 }
5364
5365 #domain hooks
5366 deploy() {
5367 _d="$1"
5368 _hooks="$2"
5369 _isEcc="$3"
5370 if [ -z "$_hooks" ]; then
5371 _usage "Usage: $PROJECT_ENTRY --deploy -d domain.com --deploy-hook cpanel [--ecc] "
5372 return 1
5373 fi
5374
5375 _initpath "$_d" "$_isEcc"
5376 if [ ! -d "$DOMAIN_PATH" ]; then
5377 _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install."
5378 _err "Can not find path:'$DOMAIN_PATH'"
5379 return 1
5380 fi
5381
5382 . "$DOMAIN_CONF"
5383
5384 _savedomainconf Le_DeployHook "$_hooks"
5385
5386 _deploy "$_d" "$_hooks"
5387 }
5388
5389 installcert() {
5390 _main_domain="$1"
5391 if [ -z "$_main_domain" ]; then
5392 _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]"
5393 return 1
5394 fi
5395
5396 _real_cert="$2"
5397 _real_key="$3"
5398 _real_ca="$4"
5399 _reload_cmd="$5"
5400 _real_fullchain="$6"
5401 _isEcc="$7"
5402
5403 _initpath "$_main_domain" "$_isEcc"
5404 if [ ! -d "$DOMAIN_PATH" ]; then
5405 _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install."
5406 _err "Can not find path:'$DOMAIN_PATH'"
5407 return 1
5408 fi
5409
5410 _savedomainconf "Le_RealCertPath" "$_real_cert"
5411 _savedomainconf "Le_RealCACertPath" "$_real_ca"
5412 _savedomainconf "Le_RealKeyPath" "$_real_key"
5413 _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64"
5414 _savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
5415
5416 _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
5417 }
5418
5419 #domain cert key ca fullchain reloadcmd backup-prefix
5420 _installcert() {
5421 _main_domain="$1"
5422 _real_cert="$2"
5423 _real_key="$3"
5424 _real_ca="$4"
5425 _real_fullchain="$5"
5426 _reload_cmd="$6"
5427 _backup_prefix="$7"
5428
5429 if [ "$_real_cert" = "$NO_VALUE" ]; then
5430 _real_cert=""
5431 fi
5432 if [ "$_real_key" = "$NO_VALUE" ]; then
5433 _real_key=""
5434 fi
5435 if [ "$_real_ca" = "$NO_VALUE" ]; then
5436 _real_ca=""
5437 fi
5438 if [ "$_reload_cmd" = "$NO_VALUE" ]; then
5439 _reload_cmd=""
5440 fi
5441 if [ "$_real_fullchain" = "$NO_VALUE" ]; then
5442 _real_fullchain=""
5443 fi
5444
5445 _backup_path="$DOMAIN_BACKUP_PATH/$_backup_prefix"
5446 mkdir -p "$_backup_path"
5447
5448 if [ "$_real_cert" ]; then
5449 _info "Installing cert to:$_real_cert"
5450 if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
5451 cp "$_real_cert" "$_backup_path/cert.bak"
5452 fi
5453 cat "$CERT_PATH" >"$_real_cert" || return 1
5454 fi
5455
5456 if [ "$_real_ca" ]; then
5457 _info "Installing CA to:$_real_ca"
5458 if [ "$_real_ca" = "$_real_cert" ]; then
5459 echo "" >>"$_real_ca"
5460 cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
5461 else
5462 if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then
5463 cp "$_real_ca" "$_backup_path/ca.bak"
5464 fi
5465 cat "$CA_CERT_PATH" >"$_real_ca" || return 1
5466 fi
5467 fi
5468
5469 if [ "$_real_key" ]; then
5470 _info "Installing key to:$_real_key"
5471 if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
5472 cp "$_real_key" "$_backup_path/key.bak"
5473 fi
5474 if [ -f "$_real_key" ]; then
5475 cat "$CERT_KEY_PATH" >"$_real_key" || return 1
5476 else
5477 cat "$CERT_KEY_PATH" >"$_real_key" || return 1
5478 chmod 600 "$_real_key"
5479 fi
5480 fi
5481
5482 if [ "$_real_fullchain" ]; then
5483 _info "Installing full chain to:$_real_fullchain"
5484 if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
5485 cp "$_real_fullchain" "$_backup_path/fullchain.bak"
5486 fi
5487 cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
5488 fi
5489
5490 if [ "$_reload_cmd" ]; then
5491 _info "Run reload cmd: $_reload_cmd"
5492 if (
5493 export CERT_PATH
5494 export CERT_KEY_PATH
5495 export CA_CERT_PATH
5496 export CERT_FULLCHAIN_PATH
5497 export Le_Domain="$_main_domain"
5498 cd "$DOMAIN_PATH" && eval "$_reload_cmd"
5499 ); then
5500 _info "$(__green "Reload success")"
5501 else
5502 _err "Reload error for :$Le_Domain"
5503 fi
5504 fi
5505
5506 }
5507
5508 __read_password() {
5509 unset _pp
5510 prompt="Enter Password:"
5511 while IFS= read -p "$prompt" -r -s -n 1 char; do
5512 if [ "$char" = $'\0' ]; then
5513 break
5514 fi
5515 prompt='*'
5516 _pp="$_pp$char"
5517 done
5518 echo "$_pp"
5519 }
5520
5521 _install_win_taskscheduler() {
5522 _lesh="$1"
5523 _centry="$2"
5524 _randomminute="$3"
5525 if ! _exists cygpath; then
5526 _err "cygpath not found"
5527 return 1
5528 fi
5529 if ! _exists schtasks; then
5530 _err "schtasks.exe is not found, are you on Windows?"
5531 return 1
5532 fi
5533 _winbash="$(cygpath -w $(which bash))"
5534 _debug _winbash "$_winbash"
5535 if [ -z "$_winbash" ]; then
5536 _err "can not find bash path"
5537 return 1
5538 fi
5539 _myname="$(whoami)"
5540 _debug "_myname" "$_myname"
5541 if [ -z "$_myname" ]; then
5542 _err "can not find my user name"
5543 return 1
5544 fi
5545 _debug "_lesh" "$_lesh"
5546
5547 _info "To install scheduler task in your Windows account, you must input your windows password."
5548 _info "$PROJECT_NAME doesn't save your password."
5549 _info "Please input your Windows password for: $(__green "$_myname")"
5550 _password="$(__read_password)"
5551 #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
5552 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
5553 echo
5554
5555 }
5556
5557 _uninstall_win_taskscheduler() {
5558 if ! _exists schtasks; then
5559 _err "schtasks.exe is not found, are you on Windows?"
5560 return 1
5561 fi
5562 if ! echo SCHTASKS /query /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null; then
5563 _debug "scheduler $_WINDOWS_SCHEDULER_NAME is not found."
5564 else
5565 _info "Removing $_WINDOWS_SCHEDULER_NAME"
5566 echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null
5567 fi
5568 }
5569
5570 #confighome
5571 installcronjob() {
5572 _c_home="$1"
5573 _initpath
5574 _CRONTAB="crontab"
5575 if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then
5576 lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
5577 else
5578 _err "Can not install cronjob, $PROJECT_ENTRY not found."
5579 return 1
5580 fi
5581 if [ "$_c_home" ]; then
5582 _c_entry="--config-home \"$_c_home\" "
5583 fi
5584 _t=$(_time)
5585 random_minute=$(_math $_t % 60)
5586
5587 if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
5588 _CRONTAB="fcrontab"
5589 fi
5590
5591 if ! _exists "$_CRONTAB"; then
5592 if _exists cygpath && _exists schtasks.exe; then
5593 _info "It seems you are on Windows, let's install Windows scheduler task."
5594 if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then
5595 _info "Install Windows scheduler task success."
5596 return 0
5597 else
5598 _err "Install Windows scheduler task failed."
5599 return 1
5600 fi
5601 fi
5602 _err "crontab/fcrontab doesn't exist, so, we can not install cron jobs."
5603 _err "All your certs will not be renewed automatically."
5604 _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday."
5605 return 1
5606 fi
5607 _info "Installing cron job"
5608 if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then
5609 if _exists uname && uname -a | grep SunOS >/dev/null; then
5610 $_CRONTAB -l | {
5611 cat
5612 echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
5613 } | $_CRONTAB --
5614 else
5615 $_CRONTAB -l | {
5616 cat
5617 echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
5618 } | $_CRONTAB -
5619 fi
5620 fi
5621 if [ "$?" != "0" ]; then
5622 _err "Install cron job failed. You need to manually renew your certs."
5623 _err "Or you can add cronjob by yourself:"
5624 _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null"
5625 return 1
5626 fi
5627 }
5628
5629 uninstallcronjob() {
5630 _CRONTAB="crontab"
5631 if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
5632 _CRONTAB="fcrontab"
5633 fi
5634
5635 if ! _exists "$_CRONTAB"; then
5636 if _exists cygpath && _exists schtasks.exe; then
5637 _info "It seems you are on Windows, let's uninstall Windows scheduler task."
5638 if _uninstall_win_taskscheduler; then
5639 _info "Uninstall Windows scheduler task success."
5640 return 0
5641 else
5642 _err "Uninstall Windows scheduler task failed."
5643 return 1
5644 fi
5645 fi
5646 return
5647 fi
5648 _info "Removing cron job"
5649 cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")"
5650 if [ "$cr" ]; then
5651 if _exists uname && uname -a | grep solaris >/dev/null; then
5652 $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB --
5653 else
5654 $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -
5655 fi
5656 LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')"
5657 _info LE_WORKING_DIR "$LE_WORKING_DIR"
5658 if _contains "$cr" "--config-home"; then
5659 LE_CONFIG_HOME="$(echo "$cr" | cut -d ' ' -f 11 | tr -d '"')"
5660 _debug LE_CONFIG_HOME "$LE_CONFIG_HOME"
5661 fi
5662 fi
5663 _initpath
5664
5665 }
5666
5667 #domain isECC revokeReason
5668 revoke() {
5669 Le_Domain="$1"
5670 if [ -z "$Le_Domain" ]; then
5671 _usage "Usage: $PROJECT_ENTRY --revoke -d domain.com [--ecc]"
5672 return 1
5673 fi
5674
5675 _isEcc="$2"
5676 _reason="$3"
5677 if [ -z "$_reason" ]; then
5678 _reason="0"
5679 fi
5680 _initpath "$Le_Domain" "$_isEcc"
5681 if [ ! -f "$DOMAIN_CONF" ]; then
5682 _err "$Le_Domain is not a issued domain, skip."
5683 return 1
5684 fi
5685
5686 if [ ! -f "$CERT_PATH" ]; then
5687 _err "Cert for $Le_Domain $CERT_PATH is not found, skip."
5688 return 1
5689 fi
5690
5691 cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)"
5692
5693 if [ -z "$cert" ]; then
5694 _err "Cert for $Le_Domain is empty found, skip."
5695 return 1
5696 fi
5697
5698 _initAPI
5699
5700 if [ "$ACME_VERSION" = "2" ]; then
5701 data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
5702 else
5703 data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
5704 fi
5705 uri="${ACME_REVOKE_CERT}"
5706
5707 if [ -f "$CERT_KEY_PATH" ]; then
5708 _info "Try domain key first."
5709 if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then
5710 if [ -z "$response" ]; then
5711 _info "Revoke success."
5712 rm -f "$CERT_PATH"
5713 return 0
5714 else
5715 _err "Revoke error by domain key."
5716 _err "$response"
5717 fi
5718 fi
5719 else
5720 _info "Domain key file doesn't exist."
5721 fi
5722
5723 _info "Try account key."
5724
5725 if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then
5726 if [ -z "$response" ]; then
5727 _info "Revoke success."
5728 rm -f "$CERT_PATH"
5729 return 0
5730 else
5731 _err "Revoke error."
5732 _debug "$response"
5733 fi
5734 fi
5735 return 1
5736 }
5737
5738 #domain ecc
5739 remove() {
5740 Le_Domain="$1"
5741 if [ -z "$Le_Domain" ]; then
5742 _usage "Usage: $PROJECT_ENTRY --remove -d domain.com [--ecc]"
5743 return 1
5744 fi
5745
5746 _isEcc="$2"
5747
5748 _initpath "$Le_Domain" "$_isEcc"
5749 _removed_conf="$DOMAIN_CONF.removed"
5750 if [ ! -f "$DOMAIN_CONF" ]; then
5751 if [ -f "$_removed_conf" ]; then
5752 _err "$Le_Domain is already removed, You can remove the folder by yourself: $DOMAIN_PATH"
5753 else
5754 _err "$Le_Domain is not a issued domain, skip."
5755 fi
5756 return 1
5757 fi
5758
5759 if mv "$DOMAIN_CONF" "$_removed_conf"; then
5760 _info "$Le_Domain is removed, the key and cert files are in $(__green $DOMAIN_PATH)"
5761 _info "You can remove them by yourself."
5762 return 0
5763 else
5764 _err "Remove $Le_Domain failed."
5765 return 1
5766 fi
5767 }
5768
5769 #domain vtype
5770 _deactivate() {
5771 _d_domain="$1"
5772 _d_type="$2"
5773 _initpath
5774
5775 if [ "$ACME_VERSION" = "2" ]; then
5776 _identifiers="{\"type\":\"dns\",\"value\":\"$_d_domain\"}"
5777 if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
5778 _err "Can not get domain new order."
5779 return 1
5780 fi
5781 _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
5782 _debug2 _authorizations_seg "$_authorizations_seg"
5783 if [ -z "$_authorizations_seg" ]; then
5784 _err "_authorizations_seg not found."
5785 _clearup
5786 _on_issue_err "$_post_hook"
5787 return 1
5788 fi
5789
5790 authzUri="$_authorizations_seg"
5791 _debug2 "authzUri" "$authzUri"
5792 if ! _send_signed_request "$authzUri"; then
5793 _err "get to authz error."
5794 _err "_authorizations_seg" "$_authorizations_seg"
5795 _err "authzUri" "$authzUri"
5796 _clearup
5797 _on_issue_err "$_post_hook"
5798 return 1
5799 fi
5800
5801 response="$(echo "$response" | _normalizeJson)"
5802 _debug2 response "$response"
5803 _URL_NAME="url"
5804 else
5805 if ! __get_domain_new_authz "$_d_domain"; then
5806 _err "Can not get domain new authz token."
5807 return 1
5808 fi
5809
5810 authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
5811 _debug "authzUri" "$authzUri"
5812 if [ "$code" ] && [ ! "$code" = '201' ]; then
5813 _err "new-authz error: $response"
5814 return 1
5815 fi
5816 _URL_NAME="uri"
5817 fi
5818
5819 entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")"
5820 if [ -z "$entries" ]; then
5821 _info "No valid entries found."
5822 if [ -z "$thumbprint" ]; then
5823 thumbprint="$(__calc_account_thumbprint)"
5824 fi
5825 _debug "Trigger validation."
5826 vtype="$VTYPE_DNS"
5827 entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
5828 _debug entry "$entry"
5829 if [ -z "$entry" ]; then
5830 _err "Error, can not get domain token $d"
5831 return 1
5832 fi
5833 token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
5834 _debug token "$token"
5835
5836 uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
5837 _debug uri "$uri"
5838
5839 keyauthorization="$token.$thumbprint"
5840 _debug keyauthorization "$keyauthorization"
5841 __trigger_validation "$uri" "$keyauthorization"
5842
5843 fi
5844
5845 _d_i=0
5846 _d_max_retry=$(echo "$entries" | wc -l)
5847 while [ "$_d_i" -lt "$_d_max_retry" ]; do
5848 _info "Deactivate: $_d_domain"
5849 _d_i="$(_math $_d_i + 1)"
5850 entry="$(echo "$entries" | sed -n "${_d_i}p")"
5851 _debug entry "$entry"
5852
5853 if [ -z "$entry" ]; then
5854 _info "No more valid entry found."
5855 break
5856 fi
5857
5858 _vtype="$(echo "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')"
5859 _debug _vtype "$_vtype"
5860 _info "Found $_vtype"
5861
5862 uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')"
5863 _debug uri "$uri"
5864
5865 if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then
5866 _info "Skip $_vtype"
5867 continue
5868 fi
5869
5870 _info "Deactivate: $_vtype"
5871
5872 if [ "$ACME_VERSION" = "2" ]; then
5873 _djson="{\"status\":\"deactivated\"}"
5874 else
5875 _djson="{\"resource\": \"authz\", \"status\":\"deactivated\"}"
5876 fi
5877
5878 if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then
5879 _info "Deactivate: $_vtype success."
5880 else
5881 _err "Can not deactivate $_vtype."
5882 break
5883 fi
5884
5885 done
5886 _debug "$_d_i"
5887 if [ "$_d_i" -eq "$_d_max_retry" ]; then
5888 _info "Deactivated success!"
5889 else
5890 _err "Deactivate failed."
5891 fi
5892
5893 }
5894
5895 deactivate() {
5896 _d_domain_list="$1"
5897 _d_type="$2"
5898 _initpath
5899 _initAPI
5900 _debug _d_domain_list "$_d_domain_list"
5901 if [ -z "$(echo $_d_domain_list | cut -d , -f 1)" ]; then
5902 _usage "Usage: $PROJECT_ENTRY --deactivate -d domain.com [-d domain.com]"
5903 return 1
5904 fi
5905 for _d_dm in $(echo "$_d_domain_list" | tr ',' ' '); do
5906 if [ -z "$_d_dm" ] || [ "$_d_dm" = "$NO_VALUE" ]; then
5907 continue
5908 fi
5909 if ! _deactivate "$_d_dm" "$_d_type"; then
5910 return 1
5911 fi
5912 done
5913 }
5914
5915 # Detect profile file if not specified as environment variable
5916 _detect_profile() {
5917 if [ -n "$PROFILE" -a -f "$PROFILE" ]; then
5918 echo "$PROFILE"
5919 return
5920 fi
5921
5922 DETECTED_PROFILE=''
5923 SHELLTYPE="$(basename "/$SHELL")"
5924
5925 if [ "$SHELLTYPE" = "bash" ]; then
5926 if [ -f "$HOME/.bashrc" ]; then
5927 DETECTED_PROFILE="$HOME/.bashrc"
5928 elif [ -f "$HOME/.bash_profile" ]; then
5929 DETECTED_PROFILE="$HOME/.bash_profile"
5930 fi
5931 elif [ "$SHELLTYPE" = "zsh" ]; then
5932 DETECTED_PROFILE="$HOME/.zshrc"
5933 fi
5934
5935 if [ -z "$DETECTED_PROFILE" ]; then
5936 if [ -f "$HOME/.profile" ]; then
5937 DETECTED_PROFILE="$HOME/.profile"
5938 elif [ -f "$HOME/.bashrc" ]; then
5939 DETECTED_PROFILE="$HOME/.bashrc"
5940 elif [ -f "$HOME/.bash_profile" ]; then
5941 DETECTED_PROFILE="$HOME/.bash_profile"
5942 elif [ -f "$HOME/.zshrc" ]; then
5943 DETECTED_PROFILE="$HOME/.zshrc"
5944 fi
5945 fi
5946
5947 echo "$DETECTED_PROFILE"
5948 }
5949
5950 _initconf() {
5951 _initpath
5952 if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
5953 echo "
5954
5955 #LOG_FILE=\"$DEFAULT_LOG_FILE\"
5956 #LOG_LEVEL=1
5957
5958 #AUTO_UPGRADE=\"1\"
5959
5960 #NO_TIMESTAMP=1
5961
5962 " >"$ACCOUNT_CONF_PATH"
5963 fi
5964 }
5965
5966 # nocron
5967 _precheck() {
5968 _nocron="$1"
5969
5970 if ! _exists "curl" && ! _exists "wget"; then
5971 _err "Please install curl or wget first, we need to access http resources."
5972 return 1
5973 fi
5974
5975 if [ -z "$_nocron" ]; then
5976 if ! _exists "crontab" && ! _exists "fcrontab"; then
5977 if _exists cygpath && _exists schtasks.exe; then
5978 _info "It seems you are on Windows, we will install Windows scheduler task."
5979 else
5980 _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
5981 _err "We need to set cron job to renew the certs automatically."
5982 _err "Otherwise, your certs will not be able to be renewed automatically."
5983 if [ -z "$FORCE" ]; then
5984 _err "Please add '--force' and try install again to go without crontab."
5985 _err "./$PROJECT_ENTRY --install --force"
5986 return 1
5987 fi
5988 fi
5989 fi
5990 fi
5991
5992 if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
5993 _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
5994 _err "We need openssl to generate keys."
5995 return 1
5996 fi
5997
5998 if ! _exists "socat"; then
5999 _err "It is recommended to install socat first."
6000 _err "We use socat for standalone server if you use standalone mode."
6001 _err "If you don't use standalone mode, just ignore this warning."
6002 fi
6003
6004 return 0
6005 }
6006
6007 _setShebang() {
6008 _file="$1"
6009 _shebang="$2"
6010 if [ -z "$_shebang" ]; then
6011 _usage "Usage: file shebang"
6012 return 1
6013 fi
6014 cp "$_file" "$_file.tmp"
6015 echo "$_shebang" >"$_file"
6016 sed -n 2,99999p "$_file.tmp" >>"$_file"
6017 rm -f "$_file.tmp"
6018 }
6019
6020 #confighome
6021 _installalias() {
6022 _c_home="$1"
6023 _initpath
6024
6025 _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env"
6026 if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then
6027 echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile"
6028 echo "$(cat "$_envfile")" | sed "s|^alias le.*$||" >"$_envfile"
6029 echo "$(cat "$_envfile")" | sed "s|^alias le.sh.*$||" >"$_envfile"
6030 fi
6031
6032 if [ "$_c_home" ]; then
6033 _c_entry=" --config-home '$_c_home'"
6034 fi
6035
6036 _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
6037 if [ "$_c_home" ]; then
6038 _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\""
6039 else
6040 _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile"
6041 fi
6042 _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6043
6044 _profile="$(_detect_profile)"
6045 if [ "$_profile" ]; then
6046 _debug "Found profile: $_profile"
6047 _info "Installing alias to '$_profile'"
6048 _setopt "$_profile" ". \"$_envfile\""
6049 _info "OK, Close and reopen your terminal to start using $PROJECT_NAME"
6050 else
6051 _info "No profile is found, you will need to go into $LE_WORKING_DIR to use $PROJECT_NAME"
6052 fi
6053
6054 #for csh
6055 _cshfile="$LE_WORKING_DIR/$PROJECT_ENTRY.csh"
6056 _csh_profile="$HOME/.cshrc"
6057 if [ -f "$_csh_profile" ]; then
6058 _info "Installing alias to '$_csh_profile'"
6059 _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
6060 if [ "$_c_home" ]; then
6061 _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
6062 else
6063 _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile"
6064 fi
6065 _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6066 _setopt "$_csh_profile" "source \"$_cshfile\""
6067 fi
6068
6069 #for tcsh
6070 _tcsh_profile="$HOME/.tcshrc"
6071 if [ -f "$_tcsh_profile" ]; then
6072 _info "Installing alias to '$_tcsh_profile'"
6073 _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\""
6074 if [ "$_c_home" ]; then
6075 _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\""
6076 fi
6077 _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\""
6078 _setopt "$_tcsh_profile" "source \"$_cshfile\""
6079 fi
6080
6081 }
6082
6083 # nocron confighome noprofile
6084 install() {
6085
6086 if [ -z "$LE_WORKING_DIR" ]; then
6087 LE_WORKING_DIR="$DEFAULT_INSTALL_HOME"
6088 fi
6089
6090 _nocron="$1"
6091 _c_home="$2"
6092 _noprofile="$3"
6093 if ! _initpath; then
6094 _err "Install failed."
6095 return 1
6096 fi
6097 if [ "$_nocron" ]; then
6098 _debug "Skip install cron job"
6099 fi
6100
6101 if [ "$_ACME_IN_CRON" != "1" ]; then
6102 if ! _precheck "$_nocron"; then
6103 _err "Pre-check failed, can not install."
6104 return 1
6105 fi
6106 fi
6107
6108 if [ -z "$_c_home" ] && [ "$LE_CONFIG_HOME" != "$LE_WORKING_DIR" ]; then
6109 _info "Using config home: $LE_CONFIG_HOME"
6110 _c_home="$LE_CONFIG_HOME"
6111 fi
6112
6113 #convert from le
6114 if [ -d "$HOME/.le" ]; then
6115 for envfile in "le.env" "le.sh.env"; do
6116 if [ -f "$HOME/.le/$envfile" ]; then
6117 if grep "le.sh" "$HOME/.le/$envfile" >/dev/null; then
6118 _upgrading="1"
6119 _info "You are upgrading from le.sh"
6120 _info "Renaming \"$HOME/.le\" to $LE_WORKING_DIR"
6121 mv "$HOME/.le" "$LE_WORKING_DIR"
6122 mv "$LE_WORKING_DIR/$envfile" "$LE_WORKING_DIR/$PROJECT_ENTRY.env"
6123 break
6124 fi
6125 fi
6126 done
6127 fi
6128
6129 _info "Installing to $LE_WORKING_DIR"
6130
6131 if [ ! -d "$LE_WORKING_DIR" ]; then
6132 if ! mkdir -p "$LE_WORKING_DIR"; then
6133 _err "Can not create working dir: $LE_WORKING_DIR"
6134 return 1
6135 fi
6136
6137 chmod 700 "$LE_WORKING_DIR"
6138 fi
6139
6140 if [ ! -d "$LE_CONFIG_HOME" ]; then
6141 if ! mkdir -p "$LE_CONFIG_HOME"; then
6142 _err "Can not create config dir: $LE_CONFIG_HOME"
6143 return 1
6144 fi
6145
6146 chmod 700 "$LE_CONFIG_HOME"
6147 fi
6148
6149 cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
6150
6151 if [ "$?" != "0" ]; then
6152 _err "Install failed, can not copy $PROJECT_ENTRY"
6153 return 1
6154 fi
6155
6156 _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
6157
6158 if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then
6159 _installalias "$_c_home"
6160 fi
6161
6162 for subf in $_SUB_FOLDERS; do
6163 if [ -d "$subf" ]; then
6164 mkdir -p "$LE_WORKING_DIR/$subf"
6165 cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/
6166 fi
6167 done
6168
6169 if [ ! -f "$ACCOUNT_CONF_PATH" ]; then
6170 _initconf
6171 fi
6172
6173 if [ "$_DEFAULT_ACCOUNT_CONF_PATH" != "$ACCOUNT_CONF_PATH" ]; then
6174 _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
6175 fi
6176
6177 if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then
6178 _saveaccountconf "CERT_HOME" "$CERT_HOME"
6179 fi
6180
6181 if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then
6182 _saveaccountconf "ACCOUNT_KEY_PATH" "$ACCOUNT_KEY_PATH"
6183 fi
6184
6185 if [ -z "$_nocron" ]; then
6186 installcronjob "$_c_home"
6187 fi
6188
6189 if [ -z "$NO_DETECT_SH" ]; then
6190 #Modify shebang
6191 if _exists bash; then
6192 _bash_path="$(bash -c "command -v bash 2>/dev/null")"
6193 if [ -z "$_bash_path" ]; then
6194 _bash_path="$(bash -c 'echo $SHELL')"
6195 fi
6196 fi
6197 if [ "$_bash_path" ]; then
6198 _info "Good, bash is found, so change the shebang to use bash as preferred."
6199 _shebang='#!'"$_bash_path"
6200 _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang"
6201 for subf in $_SUB_FOLDERS; do
6202 if [ -d "$LE_WORKING_DIR/$subf" ]; then
6203 for _apifile in "$LE_WORKING_DIR/$subf/"*.sh; do
6204 _setShebang "$_apifile" "$_shebang"
6205 done
6206 fi
6207 done
6208 fi
6209 fi
6210
6211 _info OK
6212 }
6213
6214 # nocron
6215 uninstall() {
6216 _nocron="$1"
6217 if [ -z "$_nocron" ]; then
6218 uninstallcronjob
6219 fi
6220 _initpath
6221
6222 _uninstallalias
6223
6224 rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY"
6225 _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\", you can remove them by yourself."
6226
6227 }
6228
6229 _uninstallalias() {
6230 _initpath
6231
6232 _profile="$(_detect_profile)"
6233 if [ "$_profile" ]; then
6234 _info "Uninstalling alias from: '$_profile'"
6235 text="$(cat "$_profile")"
6236 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.env\"$||" >"$_profile"
6237 fi
6238
6239 _csh_profile="$HOME/.cshrc"
6240 if [ -f "$_csh_profile" ]; then
6241 _info "Uninstalling alias from: '$_csh_profile'"
6242 text="$(cat "$_csh_profile")"
6243 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_csh_profile"
6244 fi
6245
6246 _tcsh_profile="$HOME/.tcshrc"
6247 if [ -f "$_tcsh_profile" ]; then
6248 _info "Uninstalling alias from: '$_csh_profile'"
6249 text="$(cat "$_tcsh_profile")"
6250 echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_tcsh_profile"
6251 fi
6252
6253 }
6254
6255 cron() {
6256 export _ACME_IN_CRON=1
6257 _initpath
6258 _info "$(__green "===Starting cron===")"
6259 if [ "$AUTO_UPGRADE" = "1" ]; then
6260 export LE_WORKING_DIR
6261 (
6262 if ! upgrade; then
6263 _err "Cron:Upgrade failed!"
6264 return 1
6265 fi
6266 )
6267 . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null
6268
6269 if [ -t 1 ]; then
6270 __INTERACTIVE="1"
6271 fi
6272
6273 _info "Auto upgraded to: $VER"
6274 fi
6275 renewAll
6276 _ret="$?"
6277 _ACME_IN_CRON=""
6278 _info "$(__green "===End cron===")"
6279 exit $_ret
6280 }
6281
6282 version() {
6283 echo "$PROJECT"
6284 echo "v$VER"
6285 }
6286
6287 # subject content hooks code
6288 _send_notify() {
6289 _nsubject="$1"
6290 _ncontent="$2"
6291 _nhooks="$3"
6292 _nerror="$4"
6293
6294 if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then
6295 _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, disabled, just return."
6296 return 0
6297 fi
6298
6299 if [ -z "$_nhooks" ]; then
6300 _debug "The NOTIFY_HOOK is empty, just return."
6301 return 0
6302 fi
6303
6304 _send_err=0
6305 for _n_hook in $(echo "$_nhooks" | tr ',' " "); do
6306 _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")"
6307 _info "Sending via: $_n_hook"
6308 _debug "Found $_n_hook_file for $_n_hook"
6309 if [ -z "$_n_hook_file" ]; then
6310 _err "Can not find the hook file for $_n_hook"
6311 continue
6312 fi
6313 if ! (
6314 if ! . "$_n_hook_file"; then
6315 _err "Load file $_n_hook_file error. Please check your api file and try again."
6316 return 1
6317 fi
6318
6319 d_command="${_n_hook}_send"
6320 if ! _exists "$d_command"; then
6321 _err "It seems that your api file is not correct, it must have a function named: $d_command"
6322 return 1
6323 fi
6324
6325 if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then
6326 _err "Error send message by $d_command"
6327 return 1
6328 fi
6329
6330 return 0
6331 ); then
6332 _err "Set $_n_hook_file error."
6333 _send_err=1
6334 else
6335 _info "$_n_hook $(__green Success)"
6336 fi
6337 done
6338 return $_send_err
6339
6340 }
6341
6342 # hook
6343 _set_notify_hook() {
6344 _nhooks="$1"
6345
6346 _test_subject="Hello, this is a notification from $PROJECT_NAME"
6347 _test_content="If you receive this message, your notification works."
6348
6349 _send_notify "$_test_subject" "$_test_content" "$_nhooks" 0
6350
6351 }
6352
6353 #[hook] [level] [mode]
6354 setnotify() {
6355 _nhook="$1"
6356 _nlevel="$2"
6357 _nmode="$3"
6358
6359 _initpath
6360
6361 if [ -z "$_nhook$_nlevel$_nmode" ]; then
6362 _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook mailgun] [--notify-level $NOTIFY_LEVEL_DEFAULT] [--notify-mode $NOTIFY_MODE_DEFAULT]"
6363 _usage "$_NOTIFY_WIKI"
6364 return 1
6365 fi
6366
6367 if [ "$_nlevel" ]; then
6368 _info "Set notify level to: $_nlevel"
6369 export "NOTIFY_LEVEL=$_nlevel"
6370 _saveaccountconf "NOTIFY_LEVEL" "$NOTIFY_LEVEL"
6371 fi
6372
6373 if [ "$_nmode" ]; then
6374 _info "Set notify mode to: $_nmode"
6375 export "NOTIFY_MODE=$_nmode"
6376 _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE"
6377 fi
6378
6379 if [ "$_nhook" ]; then
6380 _info "Set notify hook to: $_nhook"
6381 if [ "$_nhook" = "$NO_VALUE" ]; then
6382 _info "Clear notify hook"
6383 _clearaccountconf "NOTIFY_HOOK"
6384 else
6385 if _set_notify_hook "$_nhook"; then
6386 export NOTIFY_HOOK="$_nhook"
6387 _saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK"
6388 return 0
6389 else
6390 _err "Can not set notify hook to: $_nhook"
6391 return 1
6392 fi
6393 fi
6394 fi
6395
6396 }
6397
6398 showhelp() {
6399 _initpath
6400 version
6401 echo "Usage: $PROJECT_ENTRY command ...[parameters]....
6402 Commands:
6403 --help, -h Show this help message.
6404 --version, -v Show version info.
6405 --install Install $PROJECT_NAME to your system.
6406 --uninstall Uninstall $PROJECT_NAME, and uninstall the cron job.
6407 --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT.
6408 --issue Issue a cert.
6409 --signcsr Issue a cert from an existing csr.
6410 --deploy Deploy the cert to your server.
6411 --install-cert Install the issued cert to apache/nginx or any other server.
6412 --renew, -r Renew a cert.
6413 --renew-all Renew all the certs.
6414 --revoke Revoke a cert.
6415 --remove Remove the cert from list of certs known to $PROJECT_NAME.
6416 --list List all the certs.
6417 --showcsr Show the content of a csr.
6418 --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.
6419 --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
6420 --cron Run cron job to renew all the certs.
6421 --toPkcs Export the certificate and key to a pfx file.
6422 --toPkcs8 Convert to pkcs8 format.
6423 --update-account Update account info.
6424 --register-account Register account key.
6425 --deactivate-account Deactivate the account.
6426 --create-account-key Create an account private key, professional use.
6427 --create-domain-key Create an domain private key, professional use.
6428 --createCSR, -ccsr Create CSR , professional use.
6429 --deactivate Deactivate the domain authz, professional use.
6430 --set-notify Set the cron notification hook, level or mode.
6431 --set-default-ca Used with '--server' , to set the default CA to use to use.
6432
6433
6434 Parameters:
6435 --domain, -d domain.tld Specifies a domain, used to issue, renew or revoke etc.
6436 --challenge-alias domain.tld The challenge domain alias for DNS alias mode.
6437 See: $_DNS_ALIAS_WIKI
6438
6439 --domain-alias domain.tld The domain alias for DNS alias mode.
6440 See: $_DNS_ALIAS_WIKI
6441
6442 --preferred-chain CHAIN If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
6443 If no match, the default offered chain will be used. (default: empty)
6444 See: $_PREFERRED_CHAIN_WIKI
6445
6446 --force, -f Used to force to install or force to renew a cert immediately.
6447 --staging, --test Use staging server, just for test.
6448 --debug Output debug info.
6449 --output-insecure Output all the sensitive messages.
6450 By default all the credentials/sensitive messages are hidden from the output/debug/log for security.
6451
6452 --webroot, -w /path/to/webroot Specifies the web root folder for web root mode.
6453 --standalone Use standalone mode.
6454 --alpn Use standalone alpn mode.
6455 --stateless Use stateless mode.
6456 See: $_STATELESS_WIKI
6457
6458 --apache Use apache mode.
6459 --dns [dns_hook] Use dns mode or dns api.
6460 See: $_DNS_API_WIKI
6461
6462 --dnssleep 300 The time in seconds to wait for all the txt records to propagate in dns api mode.
6463 It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically.
6464
6465 --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
6466 --accountkeylength, -ak [2048] Specifies the account key length: 2048, 3072, 4096
6467 --log [/path/to/logfile] Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
6468 --log-level 1|2 Specifies the log level, default is 1.
6469 --syslog [0|3|6|7] Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
6470
6471 --eab-kid EAB_KID Key Identifier for External Account Binding.
6472 --eab-hmac-key EAB_HMAC_KEY HMAC key for External Account Binding.
6473
6474
6475 These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert:
6476
6477 --cert-file After issue/renew, the cert will be copied to this path.
6478 --key-file After issue/renew, the key will be copied to this path.
6479 --ca-file After issue/renew, the intermediate cert will be copied to this path.
6480 --fullchain-file After issue/renew, the fullchain cert will be copied to this path.
6481
6482 --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
6483
6484 --server SERVER ACME Directory Resource URI. (default: $DEFAULT_CA)
6485 See: $_SERVER_WIKI
6486
6487 --accountconf Specifies a customized account config file.
6488 --home Specifies the home dir for $PROJECT_NAME.
6489 --cert-home Specifies the home dir to save all the certs, only valid for '--install' command.
6490 --config-home Specifies the home dir to save all the configurations.
6491 --useragent Specifies the user agent string. it will be saved for future use too.
6492 --accountemail, -m Specifies the account email, only valid for the '--install' and '--update-account' command.
6493 --accountkey Specifies the account key path, only valid for the '--install' command.
6494 --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
6495 --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
6496 --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer.
6497 --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses.
6498 --listraw Only used for '--list' command, list the certs in raw format.
6499 --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal.
6500 --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
6501 --ca-bundle Specifies the path to the CA certificate bundle to verify api server's certificate.
6502 --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl.
6503 --nocron Only valid for '--install' command, which means: do not install the default cron job.
6504 In this case, the certs will not be renewed automatically.
6505
6506 --noprofile Only valid for '--install' command, which means: do not install aliases to user profile.
6507 --no-color Do not output color text.
6508 --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
6509 --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
6510 --csr Specifies the input csr.
6511 --pre-hook Command to be run before obtaining any certificates.
6512 --post-hook Command to be run after attempting to obtain/renew certificates. No matter the obtain/renew is success or failed.
6513 --renew-hook Command to be run once for each successfully renewed certificate.
6514 --deploy-hook The hook file to deploy cert
6515 --ocsp-must-staple, --ocsp Generate ocsp must Staple extension.
6516 --always-force-new-domain-key Generate new domain key when renewal. Otherwise, the domain key is not changed by default.
6517 --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future.
6518 --listen-v4 Force standalone/tls server to listen at ipv4.
6519 --listen-v6 Force standalone/tls server to listen at ipv6.
6520 --openssl-bin Specifies a custom openssl bin location.
6521 --use-wget Force to use wget, if you have both curl and wget installed.
6522 --yes-I-know-dns-manual-mode-enough-go-ahead-please Force to use dns manual mode.
6523 See: $_DNS_MANUAL_WIKI
6524
6525 --branch, -b Only valid for '--upgrade' command, specifies the branch name to upgrade to.
6526
6527 --notify-level 0|1|2|3 Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT.
6528 0: disabled, no notification will be sent.
6529 1: send notifications only when there is an error.
6530 2: send notifications when a cert is successfully renewed, or there is an error.
6531 3: send notifications when a cert is skipped, renewed, or error.
6532
6533 --notify-mode 0|1 Set notification mode. Default value is $NOTIFY_MODE_DEFAULT.
6534 0: Bulk mode. Send all the domain's notifications in one message(mail).
6535 1: Cert mode. Send a message for every single cert.
6536
6537 --notify-hook [hookname] Set the notify hook
6538 --revoke-reason [0-10] The reason for '--revoke' command.
6539 See: $_REVOKE_WIKI
6540
6541
6542 "
6543 }
6544
6545 # nocron noprofile
6546 _installOnline() {
6547 _info "Installing from online archive."
6548 _nocron="$1"
6549 _noprofile="$2"
6550 if [ ! "$BRANCH" ]; then
6551 BRANCH="master"
6552 fi
6553
6554 target="$PROJECT/archive/$BRANCH.tar.gz"
6555 _info "Downloading $target"
6556 localname="$BRANCH.tar.gz"
6557 if ! _get "$target" >$localname; then
6558 _err "Download error."
6559 return 1
6560 fi
6561 (
6562 _info "Extracting $localname"
6563 if ! (tar xzf $localname || gtar xzf $localname); then
6564 _err "Extraction error."
6565 exit 1
6566 fi
6567
6568 cd "$PROJECT_NAME-$BRANCH"
6569 chmod +x $PROJECT_ENTRY
6570 if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then
6571 _info "Install success!"
6572 _initpath
6573 _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
6574 fi
6575
6576 cd ..
6577
6578 rm -rf "$PROJECT_NAME-$BRANCH"
6579 rm -f "$localname"
6580 )
6581 }
6582
6583 _getRepoHash() {
6584 _hash_path=$1
6585 shift
6586 _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path"
6587 _get $_hash_url | tr -d "\r\n" | tr '{},' '\n' | grep '"sha":' | cut -d '"' -f 4
6588 }
6589
6590 _getUpgradeHash() {
6591 _b="$BRANCH"
6592 if [ -z "$_b" ]; then
6593 _b="master"
6594 fi
6595 _hash=$(_getRepoHash "heads/$_b")
6596 if [ -z "$_hash" ]; then _hash=$(_getRepoHash "tags/$_b"); fi
6597 echo $_hash
6598 }
6599
6600 upgrade() {
6601 if (
6602 _initpath
6603 [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already uptodate!" && exit 0
6604 export LE_WORKING_DIR
6605 cd "$LE_WORKING_DIR"
6606 _installOnline "nocron" "noprofile"
6607 ); then
6608 _info "Upgrade success!"
6609 exit 0
6610 else
6611 _err "Upgrade failed!"
6612 exit 1
6613 fi
6614 }
6615
6616 _processAccountConf() {
6617 if [ "$_useragent" ]; then
6618 _saveaccountconf "USER_AGENT" "$_useragent"
6619 elif [ "$USER_AGENT" ] && [ "$USER_AGENT" != "$DEFAULT_USER_AGENT" ]; then
6620 _saveaccountconf "USER_AGENT" "$USER_AGENT"
6621 fi
6622
6623 if [ "$_openssl_bin" ]; then
6624 _saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin"
6625 elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then
6626 _saveaccountconf "ACME_OPENSSL_BIN" "$ACME_OPENSSL_BIN"
6627 fi
6628
6629 if [ "$_auto_upgrade" ]; then
6630 _saveaccountconf "AUTO_UPGRADE" "$_auto_upgrade"
6631 elif [ "$AUTO_UPGRADE" ]; then
6632 _saveaccountconf "AUTO_UPGRADE" "$AUTO_UPGRADE"
6633 fi
6634
6635 if [ "$_use_wget" ]; then
6636 _saveaccountconf "ACME_USE_WGET" "$_use_wget"
6637 elif [ "$ACME_USE_WGET" ]; then
6638 _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET"
6639 fi
6640
6641 }
6642
6643 _checkSudo() {
6644 if [ "$SUDO_GID" ] && [ "$SUDO_COMMAND" ] && [ "$SUDO_USER" ] && [ "$SUDO_UID" ]; then
6645 if [ "$SUDO_USER" = "root" ] && [ "$SUDO_UID" = "0" ]; then
6646 #it's root using sudo, no matter it's using sudo or not, just fine
6647 return 0
6648 fi
6649 if [ -n "$SUDO_COMMAND" ]; then
6650 #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`
6651 _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1
6652 return $?
6653 fi
6654 #otherwise
6655 return 1
6656 fi
6657 return 0
6658 }
6659
6660 #server
6661 _selectServer() {
6662 _server="$1"
6663 _server_lower="$(echo "$_server" | _lower_case)"
6664 _sindex=0
6665 for snames in $CA_NAMES; do
6666 snames="$(echo "$snames" | _lower_case)"
6667 _sindex="$(_math $_sindex + 1)"
6668 _debug2 "_selectServer try snames" "$snames"
6669 for sname in $(echo "$snames" | tr ',' ' '); do
6670 if [ "$_server_lower" = "$sname" ]; then
6671 _debug2 "_selectServer match $sname"
6672 _serverdir="$(_getfield "$CA_SERVERS" $_sindex)"
6673 _debug "Selected server: $_serverdir"
6674 ACME_DIRECTORY="$_serverdir"
6675 export ACME_DIRECTORY
6676 return
6677 fi
6678 done
6679 done
6680 ACME_DIRECTORY="$_server"
6681 export ACME_DIRECTORY
6682 }
6683
6684 #url
6685 _getCAShortName() {
6686 caurl="$1"
6687 if [ -z "$caurl" ]; then
6688 caurl="$DEFAULT_CA"
6689 fi
6690 caurl_lower="$(echo $caurl | _lower_case)"
6691 _sindex=0
6692 for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do
6693 _sindex="$(_math $_sindex + 1)"
6694 if [ "$caurl_lower" = "$surl" ]; then
6695 _nindex=0
6696 for snames in $CA_NAMES; do
6697 _nindex="$(_math $_nindex + 1)"
6698 if [ $_nindex -ge $_sindex ]; then
6699 _getfield "$snames" 1
6700 return
6701 fi
6702 done
6703 fi
6704 done
6705 echo "$caurl"
6706 }
6707
6708 #set default ca to $ACME_DIRECTORY
6709 setdefaultca() {
6710 if [ -z "$ACME_DIRECTORY" ]; then
6711 _err "Please give a --server parameter."
6712 return 1
6713 fi
6714 _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY"
6715 _info "Changed default CA to: $(__green "$ACME_DIRECTORY")"
6716 }
6717
6718 _process() {
6719 _CMD=""
6720 _domain=""
6721 _altdomains="$NO_VALUE"
6722 _webroot=""
6723 _challenge_alias=""
6724 _keylength=""
6725 _accountkeylength=""
6726 _cert_file=""
6727 _key_file=""
6728 _ca_file=""
6729 _fullchain_file=""
6730 _reloadcmd=""
6731 _password=""
6732 _accountconf=""
6733 _useragent=""
6734 _accountemail=""
6735 _accountkey=""
6736 _certhome=""
6737 _confighome=""
6738 _httpport=""
6739 _tlsport=""
6740 _dnssleep=""
6741 _listraw=""
6742 _stopRenewOnError=""
6743 #_insecure=""
6744 _ca_bundle=""
6745 _ca_path=""
6746 _nocron=""
6747 _noprofile=""
6748 _ecc=""
6749 _csr=""
6750 _pre_hook=""
6751 _post_hook=""
6752 _renew_hook=""
6753 _deploy_hook=""
6754 _logfile=""
6755 _log=""
6756 _local_address=""
6757 _log_level=""
6758 _auto_upgrade=""
6759 _listen_v4=""
6760 _listen_v6=""
6761 _openssl_bin=""
6762 _syslog=""
6763 _use_wget=""
6764 _server=""
6765 _notify_hook=""
6766 _notify_level=""
6767 _notify_mode=""
6768 _revoke_reason=""
6769 _eab_kid=""
6770 _eab_hmac_key=""
6771 _preferred_chain=""
6772 while [ ${#} -gt 0 ]; do
6773 case "${1}" in
6774
6775 --help | -h)
6776 showhelp
6777 return
6778 ;;
6779 --version | -v)
6780 version
6781 return
6782 ;;
6783 --install)
6784 _CMD="install"
6785 ;;
6786 --uninstall)
6787 _CMD="uninstall"
6788 ;;
6789 --upgrade)
6790 _CMD="upgrade"
6791 ;;
6792 --issue)
6793 _CMD="issue"
6794 ;;
6795 --deploy)
6796 _CMD="deploy"
6797 ;;
6798 --signcsr)
6799 _CMD="signcsr"
6800 ;;
6801 --showcsr)
6802 _CMD="showcsr"
6803 ;;
6804 --installcert | -i | --install-cert)
6805 _CMD="installcert"
6806 ;;
6807 --renew | -r)
6808 _CMD="renew"
6809 ;;
6810 --renewAll | --renewall | --renew-all)
6811 _CMD="renewAll"
6812 ;;
6813 --revoke)
6814 _CMD="revoke"
6815 ;;
6816 --remove)
6817 _CMD="remove"
6818 ;;
6819 --list)
6820 _CMD="list"
6821 ;;
6822 --installcronjob | --install-cronjob)
6823 _CMD="installcronjob"
6824 ;;
6825 --uninstallcronjob | --uninstall-cronjob)
6826 _CMD="uninstallcronjob"
6827 ;;
6828 --cron)
6829 _CMD="cron"
6830 ;;
6831 --toPkcs)
6832 _CMD="toPkcs"
6833 ;;
6834 --toPkcs8)
6835 _CMD="toPkcs8"
6836 ;;
6837 --createAccountKey | --createaccountkey | -cak | --create-account-key)
6838 _CMD="createAccountKey"
6839 ;;
6840 --createDomainKey | --createdomainkey | -cdk | --create-domain-key)
6841 _CMD="createDomainKey"
6842 ;;
6843 --createCSR | --createcsr | -ccr)
6844 _CMD="createCSR"
6845 ;;
6846 --deactivate)
6847 _CMD="deactivate"
6848 ;;
6849 --updateaccount | --update-account)
6850 _CMD="updateaccount"
6851 ;;
6852 --registeraccount | --register-account)
6853 _CMD="registeraccount"
6854 ;;
6855 --deactivate-account)
6856 _CMD="deactivateaccount"
6857 ;;
6858 --set-notify)
6859 _CMD="setnotify"
6860 ;;
6861 --set-default-ca)
6862 _CMD="setdefaultca"
6863 ;;
6864 --domain | -d)
6865 _dvalue="$2"
6866
6867 if [ "$_dvalue" ]; then
6868 if _startswith "$_dvalue" "-"; then
6869 _err "'$_dvalue' is not a valid domain for parameter '$1'"
6870 return 1
6871 fi
6872 if _is_idn "$_dvalue" && ! _exists idn; then
6873 _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first."
6874 return 1
6875 fi
6876
6877 if _startswith "$_dvalue" "*."; then
6878 _debug "Wildcard domain"
6879 export ACME_VERSION=2
6880 fi
6881 if [ -z "$_domain" ]; then
6882 _domain="$_dvalue"
6883 else
6884 if [ "$_altdomains" = "$NO_VALUE" ]; then
6885 _altdomains="$_dvalue"
6886 else
6887 _altdomains="$_altdomains,$_dvalue"
6888 fi
6889 fi
6890 fi
6891
6892 shift
6893 ;;
6894
6895 --force | -f)
6896 FORCE="1"
6897 ;;
6898 --staging | --test)
6899 STAGE="1"
6900 ;;
6901 --server)
6902 _server="$2"
6903 _selectServer "$_server"
6904 shift
6905 ;;
6906 --debug)
6907 if [ -z "$2" ] || _startswith "$2" "-"; then
6908 DEBUG="$DEBUG_LEVEL_DEFAULT"
6909 else
6910 DEBUG="$2"
6911 shift
6912 fi
6913 ;;
6914 --output-insecure)
6915 export OUTPUT_INSECURE=1
6916 ;;
6917 --webroot | -w)
6918 wvalue="$2"
6919 if [ -z "$_webroot" ]; then
6920 _webroot="$wvalue"
6921 else
6922 _webroot="$_webroot,$wvalue"
6923 fi
6924 shift
6925 ;;
6926 --challenge-alias)
6927 cvalue="$2"
6928 _challenge_alias="$_challenge_alias$cvalue,"
6929 shift
6930 ;;
6931 --domain-alias)
6932 cvalue="$DNS_ALIAS_PREFIX$2"
6933 _challenge_alias="$_challenge_alias$cvalue,"
6934 shift
6935 ;;
6936 --standalone)
6937 wvalue="$NO_VALUE"
6938 if [ -z "$_webroot" ]; then
6939 _webroot="$wvalue"
6940 else
6941 _webroot="$_webroot,$wvalue"
6942 fi
6943 ;;
6944 --alpn)
6945 wvalue="$W_ALPN"
6946 if [ -z "$_webroot" ]; then
6947 _webroot="$wvalue"
6948 else
6949 _webroot="$_webroot,$wvalue"
6950 fi
6951 ;;
6952 --stateless)
6953 wvalue="$MODE_STATELESS"
6954 if [ -z "$_webroot" ]; then
6955 _webroot="$wvalue"
6956 else
6957 _webroot="$_webroot,$wvalue"
6958 fi
6959 ;;
6960 --local-address)
6961 lvalue="$2"
6962 _local_address="$_local_address$lvalue,"
6963 shift
6964 ;;
6965 --apache)
6966 wvalue="apache"
6967 if [ -z "$_webroot" ]; then
6968 _webroot="$wvalue"
6969 else
6970 _webroot="$_webroot,$wvalue"
6971 fi
6972 ;;
6973 --nginx)
6974 wvalue="$NGINX"
6975 if [ "$2" ] && ! _startswith "$2" "-"; then
6976 wvalue="$NGINX$2"
6977 shift
6978 fi
6979 if [ -z "$_webroot" ]; then
6980 _webroot="$wvalue"
6981 else
6982 _webroot="$_webroot,$wvalue"
6983 fi
6984 ;;
6985 --dns)
6986 wvalue="$W_DNS"
6987 if [ "$2" ] && ! _startswith "$2" "-"; then
6988 wvalue="$2"
6989 shift
6990 fi
6991 if [ -z "$_webroot" ]; then
6992 _webroot="$wvalue"
6993 else
6994 _webroot="$_webroot,$wvalue"
6995 fi
6996 ;;
6997 --dnssleep)
6998 _dnssleep="$2"
6999 Le_DNSSleep="$_dnssleep"
7000 shift
7001 ;;
7002
7003 --keylength | -k)
7004 _keylength="$2"
7005 shift
7006 ;;
7007 --accountkeylength | -ak)
7008 _accountkeylength="$2"
7009 shift
7010 ;;
7011
7012 --cert-file | --certpath)
7013 _cert_file="$2"
7014 shift
7015 ;;
7016 --key-file | --keypath)
7017 _key_file="$2"
7018 shift
7019 ;;
7020 --ca-file | --capath)
7021 _ca_file="$2"
7022 shift
7023 ;;
7024 --fullchain-file | --fullchainpath)
7025 _fullchain_file="$2"
7026 shift
7027 ;;
7028 --reloadcmd | --reloadCmd)
7029 _reloadcmd="$2"
7030 shift
7031 ;;
7032 --password)
7033 _password="$2"
7034 shift
7035 ;;
7036 --accountconf)
7037 _accountconf="$2"
7038 ACCOUNT_CONF_PATH="$_accountconf"
7039 shift
7040 ;;
7041 --home)
7042 LE_WORKING_DIR="$2"
7043 shift
7044 ;;
7045 --certhome | --cert-home)
7046 _certhome="$2"
7047 CERT_HOME="$_certhome"
7048 shift
7049 ;;
7050 --config-home)
7051 _confighome="$2"
7052 LE_CONFIG_HOME="$_confighome"
7053 shift
7054 ;;
7055 --useragent)
7056 _useragent="$2"
7057 USER_AGENT="$_useragent"
7058 shift
7059 ;;
7060 --accountemail | -m)
7061 _accountemail="$2"
7062 ACCOUNT_EMAIL="$_accountemail"
7063 shift
7064 ;;
7065 --accountkey)
7066 _accountkey="$2"
7067 ACCOUNT_KEY_PATH="$_accountkey"
7068 shift
7069 ;;
7070 --days)
7071 _days="$2"
7072 Le_RenewalDays="$_days"
7073 shift
7074 ;;
7075 --httpport)
7076 _httpport="$2"
7077 Le_HTTPPort="$_httpport"
7078 shift
7079 ;;
7080 --tlsport)
7081 _tlsport="$2"
7082 Le_TLSPort="$_tlsport"
7083 shift
7084 ;;
7085 --listraw)
7086 _listraw="raw"
7087 ;;
7088 --stopRenewOnError | --stoprenewonerror | -se)
7089 _stopRenewOnError="1"
7090 ;;
7091 --insecure)
7092 #_insecure="1"
7093 HTTPS_INSECURE="1"
7094 ;;
7095 --ca-bundle)
7096 _ca_bundle="$(_readlink "$2")"
7097 CA_BUNDLE="$_ca_bundle"
7098 shift
7099 ;;
7100 --ca-path)
7101 _ca_path="$2"
7102 CA_PATH="$_ca_path"
7103 shift
7104 ;;
7105 --nocron)
7106 _nocron="1"
7107 ;;
7108 --noprofile)
7109 _noprofile="1"
7110 ;;
7111 --no-color)
7112 export ACME_NO_COLOR=1
7113 ;;
7114 --force-color)
7115 export ACME_FORCE_COLOR=1
7116 ;;
7117 --ecc)
7118 _ecc="isEcc"
7119 ;;
7120 --csr)
7121 _csr="$2"
7122 shift
7123 ;;
7124 --pre-hook)
7125 _pre_hook="$2"
7126 shift
7127 ;;
7128 --post-hook)
7129 _post_hook="$2"
7130 shift
7131 ;;
7132 --renew-hook)
7133 _renew_hook="$2"
7134 shift
7135 ;;
7136 --deploy-hook)
7137 if [ -z "$2" ] || _startswith "$2" "-"; then
7138 _usage "Please specify a value for '--deploy-hook'"
7139 return 1
7140 fi
7141 _deploy_hook="$_deploy_hook$2,"
7142 shift
7143 ;;
7144 --ocsp-must-staple | --ocsp)
7145 Le_OCSP_Staple="1"
7146 ;;
7147 --always-force-new-domain-key)
7148 if [ -z "$2" ] || _startswith "$2" "-"; then
7149 Le_ForceNewDomainKey=1
7150 else
7151 Le_ForceNewDomainKey="$2"
7152 shift
7153 fi
7154 ;;
7155 --yes-I-know-dns-manual-mode-enough-go-ahead-please)
7156 export FORCE_DNS_MANUAL=1
7157 ;;
7158 --log | --logfile)
7159 _log="1"
7160 _logfile="$2"
7161 if _startswith "$_logfile" '-'; then
7162 _logfile=""
7163 else
7164 shift
7165 fi
7166 LOG_FILE="$_logfile"
7167 if [ -z "$LOG_LEVEL" ]; then
7168 LOG_LEVEL="$DEFAULT_LOG_LEVEL"
7169 fi
7170 ;;
7171 --log-level)
7172 _log_level="$2"
7173 LOG_LEVEL="$_log_level"
7174 shift
7175 ;;
7176 --syslog)
7177 if ! _startswith "$2" '-'; then
7178 _syslog="$2"
7179 shift
7180 fi
7181 if [ -z "$_syslog" ]; then
7182 _syslog="$SYSLOG_LEVEL_DEFAULT"
7183 fi
7184 ;;
7185 --auto-upgrade)
7186 _auto_upgrade="$2"
7187 if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then
7188 _auto_upgrade="1"
7189 else
7190 shift
7191 fi
7192 AUTO_UPGRADE="$_auto_upgrade"
7193 ;;
7194 --listen-v4)
7195 _listen_v4="1"
7196 Le_Listen_V4="$_listen_v4"
7197 ;;
7198 --listen-v6)
7199 _listen_v6="1"
7200 Le_Listen_V6="$_listen_v6"
7201 ;;
7202 --openssl-bin)
7203 _openssl_bin="$2"
7204 ACME_OPENSSL_BIN="$_openssl_bin"
7205 shift
7206 ;;
7207 --use-wget)
7208 _use_wget="1"
7209 ACME_USE_WGET="1"
7210 ;;
7211 --branch | -b)
7212 export BRANCH="$2"
7213 shift
7214 ;;
7215 --notify-hook)
7216 _nhook="$2"
7217 if _startswith "$_nhook" "-"; then
7218 _err "'$_nhook' is not a hook name for '$1'"
7219 return 1
7220 fi
7221 if [ "$_notify_hook" ]; then
7222 _notify_hook="$_notify_hook,$_nhook"
7223 else
7224 _notify_hook="$_nhook"
7225 fi
7226 shift
7227 ;;
7228 --notify-level)
7229 _nlevel="$2"
7230 if _startswith "$_nlevel" "-"; then
7231 _err "'$_nlevel' is not a integer for '$1'"
7232 return 1
7233 fi
7234 _notify_level="$_nlevel"
7235 shift
7236 ;;
7237 --notify-mode)
7238 _nmode="$2"
7239 if _startswith "$_nmode" "-"; then
7240 _err "'$_nmode' is not a integer for '$1'"
7241 return 1
7242 fi
7243 _notify_mode="$_nmode"
7244 shift
7245 ;;
7246 --revoke-reason)
7247 _revoke_reason="$2"
7248 if _startswith "$_revoke_reason" "-"; then
7249 _err "'$_revoke_reason' is not a integer for '$1'"
7250 return 1
7251 fi
7252 shift
7253 ;;
7254 --eab-kid)
7255 _eab_kid="$2"
7256 shift
7257 ;;
7258 --eab-hmac-key)
7259 _eab_hmac_key="$2"
7260 shift
7261 ;;
7262 --preferred-chain)
7263 _preferred_chain="$2"
7264 shift
7265 ;;
7266 *)
7267 _err "Unknown parameter : $1"
7268 return 1
7269 ;;
7270 esac
7271
7272 shift 1
7273 done
7274
7275 if [ "${_CMD}" != "install" ]; then
7276 if [ "$__INTERACTIVE" ] && ! _checkSudo; then
7277 if [ -z "$FORCE" ]; then
7278 #Use "echo" here, instead of _info. it's too early
7279 echo "It seems that you are using sudo, please read this link first:"
7280 echo "$_SUDO_WIKI"
7281 return 1
7282 fi
7283 fi
7284 __initHome
7285 if [ "$_log" ]; then
7286 if [ -z "$_logfile" ]; then
7287 _logfile="$DEFAULT_LOG_FILE"
7288 fi
7289 fi
7290 if [ "$_logfile" ]; then
7291 _saveaccountconf "LOG_FILE" "$_logfile"
7292 LOG_FILE="$_logfile"
7293 fi
7294
7295 if [ "$_log_level" ]; then
7296 _saveaccountconf "LOG_LEVEL" "$_log_level"
7297 LOG_LEVEL="$_log_level"
7298 fi
7299
7300 if [ "$_syslog" ]; then
7301 if _exists logger; then
7302 if [ "$_syslog" = "0" ]; then
7303 _clearaccountconf "SYS_LOG"
7304 else
7305 _saveaccountconf "SYS_LOG" "$_syslog"
7306 fi
7307 SYS_LOG="$_syslog"
7308 else
7309 _err "The 'logger' command is not found, can not enable syslog."
7310 _clearaccountconf "SYS_LOG"
7311 SYS_LOG=""
7312 fi
7313 fi
7314
7315 _processAccountConf
7316 fi
7317
7318 _debug2 LE_WORKING_DIR "$LE_WORKING_DIR"
7319
7320 if [ "$DEBUG" ]; then
7321 version
7322 if [ "$_server" ]; then
7323 _debug "Using server: $_server"
7324 fi
7325 fi
7326 _debug "Running cmd: ${_CMD}"
7327 case "${_CMD}" in
7328 install) install "$_nocron" "$_confighome" "$_noprofile" ;;
7329 uninstall) uninstall "$_nocron" ;;
7330 upgrade) upgrade ;;
7331 issue)
7332 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"
7333 ;;
7334 deploy)
7335 deploy "$_domain" "$_deploy_hook" "$_ecc"
7336 ;;
7337 signcsr)
7338 signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias"
7339 ;;
7340 showcsr)
7341 showcsr "$_csr" "$_domain"
7342 ;;
7343 installcert)
7344 installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc"
7345 ;;
7346 renew)
7347 renew "$_domain" "$_ecc"
7348 ;;
7349 renewAll)
7350 renewAll "$_stopRenewOnError"
7351 ;;
7352 revoke)
7353 revoke "$_domain" "$_ecc" "$_revoke_reason"
7354 ;;
7355 remove)
7356 remove "$_domain" "$_ecc"
7357 ;;
7358 deactivate)
7359 deactivate "$_domain,$_altdomains"
7360 ;;
7361 registeraccount)
7362 registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"
7363 ;;
7364 updateaccount)
7365 updateaccount
7366 ;;
7367 deactivateaccount)
7368 deactivateaccount
7369 ;;
7370 list)
7371 list "$_listraw" "$_domain"
7372 ;;
7373 installcronjob) installcronjob "$_confighome" ;;
7374 uninstallcronjob) uninstallcronjob ;;
7375 cron) cron ;;
7376 toPkcs)
7377 toPkcs "$_domain" "$_password" "$_ecc"
7378 ;;
7379 toPkcs8)
7380 toPkcs8 "$_domain" "$_ecc"
7381 ;;
7382 createAccountKey)
7383 createAccountKey "$_accountkeylength"
7384 ;;
7385 createDomainKey)
7386 createDomainKey "$_domain" "$_keylength"
7387 ;;
7388 createCSR)
7389 createCSR "$_domain" "$_altdomains" "$_ecc"
7390 ;;
7391 setnotify)
7392 setnotify "$_notify_hook" "$_notify_level" "$_notify_mode"
7393 ;;
7394 setdefaultca)
7395 setdefaultca
7396 ;;
7397 *)
7398 if [ "$_CMD" ]; then
7399 _err "Invalid command: $_CMD"
7400 fi
7401 showhelp
7402 return 1
7403 ;;
7404 esac
7405 _ret="$?"
7406 if [ "$_ret" != "0" ]; then
7407 return $_ret
7408 fi
7409
7410 if [ "${_CMD}" = "install" ]; then
7411 if [ "$_log" ]; then
7412 if [ -z "$LOG_FILE" ]; then
7413 LOG_FILE="$DEFAULT_LOG_FILE"
7414 fi
7415 _saveaccountconf "LOG_FILE" "$LOG_FILE"
7416 fi
7417
7418 if [ "$_log_level" ]; then
7419 _saveaccountconf "LOG_LEVEL" "$_log_level"
7420 fi
7421
7422 if [ "$_syslog" ]; then
7423 if _exists logger; then
7424 if [ "$_syslog" = "0" ]; then
7425 _clearaccountconf "SYS_LOG"
7426 else
7427 _saveaccountconf "SYS_LOG" "$_syslog"
7428 fi
7429 else
7430 _err "The 'logger' command is not found, can not enable syslog."
7431 _clearaccountconf "SYS_LOG"
7432 SYS_LOG=""
7433 fi
7434 fi
7435
7436 _processAccountConf
7437 fi
7438
7439 }
7440
7441 if [ "$INSTALLONLINE" ]; then
7442 INSTALLONLINE=""
7443 _installOnline
7444 exit
7445 fi
7446
7447 main() {
7448 [ -z "$1" ] && showhelp && return
7449 if _startswith "$1" '-'; then _process "$@"; else "$@"; fi
7450 }
7451
7452 main "$@"