]> git.proxmox.com Git - mirror_acme.sh.git/blame - le.sh
Merge pull request #133 from Neilpang/nc-timeout
[mirror_acme.sh.git] / le.sh
CommitLineData
4c3b3608 1#!/usr/bin/env bash
a63b05a9 2VER=2.0.0
4c3b3608 3PROJECT="https://github.com/Neilpang/le"
4
5DEFAULT_CA="https://acme-v01.api.letsencrypt.org"
6DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
7
bbbdcb09 8DEFAULT_USER_AGENT="le.sh client: $PROJECT"
9
4c3b3608 10STAGE_CA="https://acme-staging.api.letsencrypt.org"
11
12VTYPE_HTTP="http-01"
13VTYPE_DNS="dns-01"
14
88fab7d6 15BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----"
16END_CSR="-----END CERTIFICATE REQUEST-----"
17
18BEGIN_CERT="-----BEGIN CERTIFICATE-----"
19END_CERT="-----END CERTIFICATE-----"
20
a63b05a9 21if [[ -z "$AGREEMENT" ]] ; then
4c3b3608 22 AGREEMENT="$DEFAULT_AGREEMENT"
23fi
24
4c3b3608 25
26_info() {
a63b05a9 27 if [[ -z "$2" ]] ; then
28 echo "[$(date)] $1"
4c3b3608 29 else
a63b05a9 30 echo "[$(date)] $1"="'$2'"
4c3b3608 31 fi
32}
33
34_err() {
a63b05a9 35 _info "$@" >&2
4c3b3608 36 return 1
37}
38
c60883ef 39_debug() {
a63b05a9 40 if [[ -z "$DEBUG" ]] ; then
c60883ef 41 return
42 fi
a63b05a9 43 _err "$@"
c60883ef 44 return 0
45}
46
a63b05a9 47_debug2() {
48 if [[ "$DEBUG" -ge "2" ]] ; then
49 _debug "$@"
50 fi
51 return
52}
53
c60883ef 54_exists() {
55 cmd="$1"
a63b05a9 56 if [[ -z "$cmd" ]] ; then
c60883ef 57 _err "Usage: _exists cmd"
58 return 1
59 fi
60 command -v $cmd >/dev/null 2>&1
61 ret="$?"
a63b05a9 62 _debug2 "$cmd exists=$ret"
c60883ef 63 return $ret
64}
65
4c3b3608 66_h2b() {
67 hex=$(cat)
68 i=1
69 j=2
70 while [ '1' ] ; do
71 h=$(printf $hex | cut -c $i-$j)
a63b05a9 72 if [[ -z "$h" ]] ; then
4c3b3608 73 break;
74 fi
75 printf "\x$h"
76 let "i+=2"
77 let "j+=2"
78 done
79}
80
c60883ef 81#options file
82_sed_i() {
83 options="$1"
84 filename="$2"
a63b05a9 85 if [[ -z "$filename" ]] ; then
c60883ef 86 _err "Usage:_sed_i options filename"
87 return 1
88 fi
89
90 if sed -h 2>&1 | grep "\-i[SUFFIX]" ; then
91 _debug "Using sed -i"
92 sed -i ""
93 else
94 _debug "No -i support in sed"
95 text="$(cat $filename)"
96 echo "$text" | sed "$options" > "$filename"
97 fi
98}
99
88fab7d6 100#Usage: file startline endline
101_getfile() {
102 filename="$1"
103 startline="$2"
104 endline="$3"
a63b05a9 105 if [[ -z "$endline" ]] ; then
88fab7d6 106 _err "Usage: file startline endline"
107 return 1
108 fi
109
110 i="$(grep -n -- "$startline" $filename | cut -d : -f 1)"
a63b05a9 111 if [[ -z "$i" ]] ; then
88fab7d6 112 _err "Can not find start line: $startline"
113 return 1
114 fi
115 let "i+=1"
116 _debug i $i
117
118 j="$(grep -n -- "$endline" $filename | cut -d : -f 1)"
a63b05a9 119 if [[ -z "$j" ]] ; then
88fab7d6 120 _err "Can not find end line: $endline"
121 return 1
122 fi
123 let "j-=1"
124 _debug j $j
125
126 sed -n $i,${j}p "$filename"
127
128}
129
130#Usage: multiline
4c3b3608 131_base64() {
a63b05a9 132 if [[ "$1" ]] ; then
88fab7d6 133 openssl base64 -e
134 else
135 openssl base64 -e | tr -d '\r\n'
136 fi
137}
138
139#Usage: multiline
140_dbase64() {
a63b05a9 141 if [[ "$1" ]] ; then
88fab7d6 142 openssl base64 -d -A
143 else
144 openssl base64 -d
145 fi
146}
147
148#Usage: hashalg
149#Output Base64-encoded digest
150_digest() {
151 alg="$1"
a63b05a9 152 if [[ -z "$alg" ]] ; then
88fab7d6 153 _err "Usage: _digest hashalg"
154 return 1
155 fi
156
a63b05a9 157 if [[ "$alg" == "sha256" ]] ; then
88fab7d6 158 openssl dgst -sha256 -binary | _base64
159 else
160 _err "$alg is not supported yet"
161 return 1
162 fi
163
164}
165
166#Usage: keyfile hashalg
167#Output: Base64-encoded signature value
168_sign() {
169 keyfile="$1"
170 alg="$2"
a63b05a9 171 if [[ -z "$alg" ]] ; then
88fab7d6 172 _err "Usage: _sign keyfile hashalg"
173 return 1
174 fi
175
a63b05a9 176 if [[ "$alg" == "sha256" ]] ; then
88fab7d6 177 openssl dgst -sha256 -sign "$keyfile" | _base64
178 else
179 _err "$alg is not supported yet"
180 return 1
181 fi
182
4c3b3608 183}
184
34c27e09 185_ss() {
186 _port="$1"
edf08da6 187
188 if _exists "ss" ; then
189 _debug "Using: ss"
190 ss -ntpl | grep :$_port" "
191 return 0
192 fi
193
194 if _exists "netstat" ; then
251fc37c 195 _debug "Using: netstat"
ccb96535 196 if netstat -h 2>&1 | grep "\-p proto" >/dev/null ; then
197 #for windows version netstat tool
50a4ef9c 198 netstat -anb -p tcp | grep "LISTENING" | grep :$_port" "
ccb96535 199 else
14165e5a 200 if netstat -help 2>&1 | grep "\-p protocol" >/dev/null ; then
edf08da6 201 netstat -an -p tcp | grep LISTEN | grep :$_port" "
202 else
203 netstat -ntpl | grep :$_port" "
204 fi
ccb96535 205 fi
34c27e09 206 return 0
207 fi
edf08da6 208
34c27e09 209 return 1
210}
211
ac2d5123 212toPkcs() {
213 domain="$1"
214 pfxPassword="$2"
215 if [[ -z "$domain" ]] ; then
a63b05a9 216 echo "Usage: le.sh --toPkcs -d domain [--password pfx-password]"
ac2d5123 217 return 1
218 fi
219
220 _initpath "$domain"
221
222 if [[ "$pfxPassword" ]] ; then
223 openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
224 else
225 openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
226 fi
227
228 if [[ "$?" == "0" ]] ; then
229 _info "Success, Pfx is exported to: $CERT_PFX_PATH"
230 fi
231
232}
233
4c3b3608 234#domain [2048]
235createAccountKey() {
236 _info "Creating account key"
a63b05a9 237 if [[ -z "$1" ]] ; then
238 echo Usage: le.sh --createAccountKey -d domain.com [--accountkeylength 2048]
4c3b3608 239 return
240 fi
241
242 account=$1
243 length=$2
244
245 if [[ "$length" == "ec-"* ]] ; then
246 length=2048
247 fi
248
a63b05a9 249 if [[ -z "$2" ]] ; then
4c3b3608 250 _info "Use default length 2048"
251 length=2048
252 fi
253 _initpath
254
a63b05a9 255 if [[ -f "$ACCOUNT_KEY_PATH" ]] ; then
4c3b3608 256 _info "Account key exists, skip"
257 return
258 else
259 #generate account key
f89d991d 260 openssl genrsa $length 2>/dev/null > "$ACCOUNT_KEY_PATH"
4c3b3608 261 fi
262
263}
264
265#domain length
266createDomainKey() {
267 _info "Creating domain key"
a63b05a9 268 if [[ -z "$1" ]] ; then
269 echo Usage: le.sh --createDomainKey -d domain.com [ --keylength 2048 ]
4c3b3608 270 return
271 fi
272
273 domain=$1
274 length=$2
275 isec=""
276 if [[ "$length" == "ec-"* ]] ; then
277 isec="1"
278 length=$(printf $length | cut -d '-' -f 2-100)
279 eccname="$length"
280 fi
281
a63b05a9 282 if [[ -z "$length" ]] ; then
283 if [[ "$isec" ]] ; then
4c3b3608 284 length=256
285 else
286 length=2048
287 fi
288 fi
289 _info "Use length $length"
290
a63b05a9 291 if [[ "$isec" ]] ; then
292 if [[ "$length" == "256" ]] ; then
4c3b3608 293 eccname="prime256v1"
294 fi
a63b05a9 295 if [[ "$length" == "384" ]] ; then
4c3b3608 296 eccname="secp384r1"
297 fi
a63b05a9 298 if [[ "$length" == "521" ]] ; then
4c3b3608 299 eccname="secp521r1"
300 fi
301 _info "Using ec name: $eccname"
302 fi
303
304 _initpath $domain
305
a63b05a9 306 if [[ ! -f "$CERT_KEY_PATH" ]] || ( [[ "$FORCE" ]] && ! [[ "$IS_RENEW" ]] ); then
4c3b3608 307 #generate account key
a63b05a9 308 if [[ "$isec" ]] ; then
4c3b3608 309 openssl ecparam -name $eccname -genkey 2>/dev/null > "$CERT_KEY_PATH"
310 else
311 openssl genrsa $length 2>/dev/null > "$CERT_KEY_PATH"
312 fi
313 else
a63b05a9 314 if [[ "$IS_RENEW" ]] ; then
4c3b3608 315 _info "Domain key exists, skip"
316 return 0
317 else
318 _err "Domain key exists, do you want to overwrite the key?"
319 _err "Set FORCE=1, and try again."
320 return 1
321 fi
322 fi
323
324}
325
326# domain domainlist
327createCSR() {
328 _info "Creating csr"
a63b05a9 329 if [[ -z "$1" ]] ; then
330 echo Usage: le.sh --createCSR -d domain1.com [-d domain2.com -d domain3.com ... ]
4c3b3608 331 return
332 fi
333 domain=$1
334 _initpath $domain
335
336 domainlist=$2
337
a63b05a9 338 if [[ -f "$CSR_PATH" ]] && [[ "$IS_RENEW" ]] && [[ -z "$FORCE" ]]; then
4c3b3608 339 _info "CSR exists, skip"
340 return
341 fi
342
a63b05a9 343 if [[ -z "$domainlist" ]] || [[ "$domainlist" == "no" ]]; then
4c3b3608 344 #single domain
345 _info "Single domain" $domain
1ad65f7d 346 printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n" > "$DOMAIN_SSL_CONF"
347 openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -config "$DOMAIN_SSL_CONF" -out "$CSR_PATH"
4c3b3608 348 else
349 alt="DNS:$(echo $domainlist | sed "s/,/,DNS:/g")"
350 #multi
351 _info "Multi domain" "$alt"
352 printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt" > "$DOMAIN_SSL_CONF"
353 openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -reqexts SAN -config "$DOMAIN_SSL_CONF" -out "$CSR_PATH"
354 fi
355
356}
357
166096dc 358_urlencode() {
4c3b3608 359 __n=$(cat)
360 echo $__n | tr '/+' '_-' | tr -d '= '
361}
362
363_time2str() {
364 #BSD
365 if date -u -d@$1 2>/dev/null ; then
366 return
367 fi
368
369 #Linux
370 if date -u -r $1 2>/dev/null ; then
371 return
372 fi
373
374}
375
44df2967 376_stat() {
377 #Linux
378 if stat -c '%U:%G' "$1" 2>/dev/null ; then
379 return
380 fi
381
382 #BSD
383 if stat -f '%Su:%Sg' "$1" 2>/dev/null ; then
384 return
385 fi
386}
387
166096dc 388#keyfile
389_calcjwk() {
390 keyfile="$1"
a63b05a9 391 if [[ -z "$keyfile" ]] ; then
166096dc 392 _err "Usage: _calcjwk keyfile"
393 return 1
394 fi
395 EC_SIGN=""
396 if grep "BEGIN RSA PRIVATE KEY" "$keyfile" > /dev/null 2>&1 ; then
397 _debug "RSA key"
398 pub_exp=$(openssl rsa -in $keyfile -noout -text | grep "^publicExponent:"| cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)
a63b05a9 399 if [[ "${#pub_exp}" == "5" ]] ; then
166096dc 400 pub_exp=0$pub_exp
401 fi
a63b05a9 402 _debug2 pub_exp "$pub_exp"
166096dc 403
404 e=$(echo $pub_exp | _h2b | _base64)
a63b05a9 405 _debug2 e "$e"
166096dc 406
407 modulus=$(openssl rsa -in $keyfile -modulus -noout | cut -d '=' -f 2 )
408 n=$(echo $modulus| _h2b | _base64 | _urlencode )
409 jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
a63b05a9 410 _debug2 jwk "$jwk"
166096dc 411
412 HEADER='{"alg": "RS256", "jwk": '$jwk'}'
413 HEADERPLACE='{"nonce": "NONCE", "alg": "RS256", "jwk": '$jwk'}'
414 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" > /dev/null 2>&1 ; then
415 _debug "EC key"
416 EC_SIGN="1"
417 crv="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
a63b05a9 418 _debug2 crv $crv
166096dc 419
420 pubi="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
a63b05a9 421 _debug2 pubi $pubi
166096dc 422 let "pubi=pubi+1"
423
424 pubj="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
a63b05a9 425 _debug2 pubj $pubj
166096dc 426 let "pubj=pubj-1"
427
428 pubtext="$(openssl ec -in $keyfile -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
a63b05a9 429 _debug2 pubtext "$pubtext"
166096dc 430
431 xlen="$(printf "$pubtext" | tr -d ':' | wc -c)"
432 let "xlen=xlen/4"
a63b05a9 433 _debug2 xlen $xlen
166096dc 434
435 let "xend=xlen+1"
436 x="$(printf $pubtext | cut -d : -f 2-$xend)"
a63b05a9 437 _debug2 x $x
166096dc 438
439 x64="$(printf $x | tr -d : | _h2b | _base64 | _urlencode)"
a63b05a9 440 _debug2 x64 $x64
166096dc 441
442 let "xend+=1"
443 y="$(printf $pubtext | cut -d : -f $xend-10000)"
a63b05a9 444 _debug2 y $y
166096dc 445
446 y64="$(printf $y | tr -d : | _h2b | _base64 | _urlencode)"
a63b05a9 447 _debug2 y64 $y64
166096dc 448
449 jwk='{"kty": "EC", "crv": "'$crv'", "x": "'$x64'", "y": "'$y64'"}'
a63b05a9 450 _debug2 jwk "$jwk"
166096dc 451
452 HEADER='{"alg": "ES256", "jwk": '$jwk'}'
453 HEADERPLACE='{"nonce": "NONCE", "alg": "ES256", "jwk": '$jwk'}'
454
455 else
456 _err "Only RSA or EC key is supported."
457 return 1
458 fi
459
a63b05a9 460 _debug2 HEADER "$HEADER"
166096dc 461}
c60883ef 462# body url [needbase64]
463_post() {
464 body="$1"
465 url="$2"
466 needbase64="$3"
467
468 if _exists "curl" ; then
bbbdcb09 469 CURL="$CURL --dump-header $HTTP_HEADER "
a63b05a9 470 if [[ "$needbase64" ]] ; then
bbbdcb09 471 response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url | _base64)"
c60883ef 472 else
bbbdcb09 473 response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url)"
c60883ef 474 fi
475 else
a63b05a9 476 if [[ "$needbase64" ]] ; then
bbbdcb09 477 response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER" | _base64)"
c60883ef 478 else
bbbdcb09 479 response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER")"
c60883ef 480 fi
481 _sed_i "s/^ *//g" "$HTTP_HEADER"
482 fi
483 echo -n "$response"
484
485}
486
487# url getheader
488_get() {
489 url="$1"
490 onlyheader="$2"
491 _debug url $url
492 if _exists "curl" ; then
a63b05a9 493 if [[ "$onlyheader" ]] ; then
bbbdcb09 494 $CURL -I -A "User-Agent: $USER_AGENT" $url
c60883ef 495 else
bbbdcb09 496 $CURL -A "User-Agent: $USER_AGENT" $url
c60883ef 497 fi
498 else
bbbdcb09 499 _debug "WGET" "$WGET"
a63b05a9 500 if [[ "$onlyheader" ]] ; then
bbbdcb09 501 eval $WGET --user-agent=\"$USER_AGENT\" -S -O /dev/null $url 2>&1 | sed 's/^[ ]*//g'
c60883ef 502 else
bbbdcb09 503 eval $WGET --user-agent=\"$USER_AGENT\" -O - $url
c60883ef 504 fi
505 fi
506 ret=$?
507 return $ret
508}
166096dc 509
510# url payload needbase64 keyfile
4c3b3608 511_send_signed_request() {
512 url=$1
513 payload=$2
514 needbase64=$3
166096dc 515 keyfile=$4
a63b05a9 516 if [[ -z "$keyfile" ]] ; then
166096dc 517 keyfile="$ACCOUNT_KEY_PATH"
518 fi
4c3b3608 519 _debug url $url
520 _debug payload "$payload"
521
166096dc 522 if ! _calcjwk "$keyfile" ; then
523 return 1
524 fi
c60883ef 525
166096dc 526 payload64=$(echo -n $payload | _base64 | _urlencode)
a63b05a9 527 _debug2 payload64 $payload64
4c3b3608 528
529 nonceurl="$API/directory"
bbbdcb09 530 nonce="$(_get $nonceurl "onlyheader" | grep -o "Replay-Nonce:.*$" | head -1 | tr -d "\r\n" | cut -d ' ' -f 2)"
4c3b3608 531
532 _debug nonce "$nonce"
533
534 protected="$(printf "$HEADERPLACE" | sed "s/NONCE/$nonce/" )"
a63b05a9 535 _debug2 protected "$protected"
4c3b3608 536
166096dc 537 protected64="$(printf "$protected" | _base64 | _urlencode)"
a63b05a9 538 _debug2 protected64 "$protected64"
166096dc 539
88fab7d6 540 sig=$(echo -n "$protected64.$payload64" | _sign "$keyfile" "sha256" | _urlencode)
a63b05a9 541 _debug2 sig "$sig"
4c3b3608 542
543 body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
a63b05a9 544 _debug2 body "$body"
4c3b3608 545
bbbdcb09 546
c60883ef 547 response="$(_post "$body" $url "$needbase64" )"
4c3b3608 548
c60883ef 549 responseHeaders="$(cat $HTTP_HEADER)"
4c3b3608 550
a63b05a9 551 _debug2 responseHeaders "$responseHeaders"
552 _debug2 response "$response"
c60883ef 553 code="$(grep "^HTTP" $HTTP_HEADER | tail -1 | cut -d " " -f 2 | tr -d "\r\n" )"
4c3b3608 554 _debug code $code
555
556}
557
4c3b3608 558
559#setopt "file" "opt" "=" "value" [";"]
560_setopt() {
561 __conf="$1"
562 __opt="$2"
563 __sep="$3"
564 __val="$4"
565 __end="$5"
a63b05a9 566 if [[ -z "$__opt" ]] ; then
4c3b3608 567 echo usage: _setopt '"file" "opt" "=" "value" [";"]'
568 return
569 fi
a63b05a9 570 if [[ ! -f "$__conf" ]] ; then
4c3b3608 571 touch "$__conf"
572 fi
573
574 if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then
a63b05a9 575 _debug2 OK
4c3b3608 576 if [[ "$__val" == *"&"* ]] ; then
577 __val="$(echo $__val | sed 's/&/\\&/g')"
578 fi
579 text="$(cat $__conf)"
6dfaaa70 580 echo "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
4c3b3608 581
582 elif grep -H -n "^#$__opt$__sep" "$__conf" > /dev/null ; then
583 if [[ "$__val" == *"&"* ]] ; then
584 __val="$(echo $__val | sed 's/&/\\&/g')"
585 fi
586 text="$(cat $__conf)"
6dfaaa70 587 echo "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
4c3b3608 588
589 else
a63b05a9 590 _debug2 APP
4c3b3608 591 echo "$__opt$__sep$__val$__end" >> "$__conf"
592 fi
593 _debug "$(grep -H -n "^$__opt$__sep" $__conf)"
594}
595
596#_savedomainconf key value
597#save to domain.conf
598_savedomainconf() {
599 key="$1"
600 value="$2"
a63b05a9 601 if [[ "$DOMAIN_CONF" ]] ; then
4c3b3608 602 _setopt $DOMAIN_CONF "$key" "=" "$value"
603 else
604 _err "DOMAIN_CONF is empty, can not save $key=$value"
605 fi
606}
607
608#_saveaccountconf key value
609_saveaccountconf() {
610 key="$1"
611 value="$2"
a63b05a9 612 if [[ "$ACCOUNT_CONF_PATH" ]] ; then
281aa349 613 _setopt $ACCOUNT_CONF_PATH "$key" "=" "\"$value\""
4c3b3608 614 else
615 _err "ACCOUNT_CONF_PATH is empty, can not save $key=$value"
616 fi
617}
618
619_startserver() {
620 content="$1"
6fc1447f 621 _debug "startserver: $$"
1b2e940d 622 nchelp="$(nc -h 2>&1)"
850c1128 623
399306a1 624 if echo "$nchelp" | grep "\-q[ ,]" >/dev/null ; then
850c1128 625 _NC="nc -q 1 -l"
626 else
f76eb452 627 if echo "$nchelp" | grep "GNU netcat" >/dev/null && echo "$nchelp" | grep "\-c, \-\-close" >/dev/null ; then
628 _NC="nc -c -l"
6d60f288 629 elif echo "$nchelp" | grep "\-N" |grep "Shutdown the network socket after EOF on stdin" >/dev/null ; then
630 _NC="nc -N -l"
f76eb452 631 else
632 _NC="nc -l"
633 fi
4c3b3608 634 fi
1b2e940d 635
f76eb452 636 _debug "_NC" "$_NC"
4c3b3608 637# while true ; do
a63b05a9 638 if [[ "$DEBUG" ]] ; then
3aff11f6 639 if ! echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -p $Le_HTTPPort -vv ; then
640 echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort -vv ;
641 fi
4c3b3608 642 else
bac3b6b0 643 if ! echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -p $Le_HTTPPort > /dev/null 2>&1; then
644 echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort > /dev/null 2>&1
3aff11f6 645 fi
4c3b3608 646 fi
a63b05a9 647 if [[ "$?" != "0" ]] ; then
051c706d 648 _err "nc listen error."
6fc1447f 649 exit 1
051c706d 650 fi
4c3b3608 651# done
652}
653
6fc1447f 654_stopserver(){
4c3b3608 655 pid="$1"
6fc1447f 656 _debug "pid" "$pid"
657 if [[ -z "$pid" ]] ; then
658 return
659 fi
660
dca09ded 661 if [[ "$(ps | grep "$pid")" ]] ; then
662 _debug "Found proc process, kill it."
770dc4b2 663 kill -s 9 $pid > /dev/null
6fc1447f 664 fi
665
233e8a20 666 for ncid in $(echo $(ps | grep nc) | cut -d " " -f 1) ; do
dca09ded 667 _debug "kill $ncid"
770dc4b2 668 kill -s 9 $ncid > /dev/null
dca09ded 669 done
670
6fc1447f 671 _get "http://localhost:$Le_HTTPPort" >/dev/null 2>$1
4c3b3608 672
673}
674
675_initpath() {
676
a63b05a9 677 if [[ -z "$LE_WORKING_DIR" ]] ; then
4c3b3608 678 LE_WORKING_DIR=$HOME/.le
679 fi
680
d53289d7 681 _DEFAULT_ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf"
682
683 if [[ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]] ; then
684 source "$_DEFAULT_ACCOUNT_CONF_PATH"
685 fi
686
a63b05a9 687 if [[ -z "$ACCOUNT_CONF_PATH" ]] ; then
d53289d7 688 ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
4c3b3608 689 fi
690
a63b05a9 691 if [[ -f "$ACCOUNT_CONF_PATH" ]] ; then
4c3b3608 692 source "$ACCOUNT_CONF_PATH"
693 fi
694
281aa349 695 if [[ "$IN_CRON" ]] ; then
696 if [[ ! "$_USER_PATH_EXPORTED" ]] ; then
697 _USER_PATH_EXPORTED=1
698 export PATH="$USER_PATH:$PATH"
699 fi
700 fi
701
a63b05a9 702 if [[ -z "$API" ]] ; then
703 if [[ -z "$STAGE" ]] ; then
4c3b3608 704 API="$DEFAULT_CA"
705 else
706 API="$STAGE_CA"
707 _info "Using stage api:$API"
708 fi
709 fi
710
a63b05a9 711 if [[ -z "$ACME_DIR" ]] ; then
4c3b3608 712 ACME_DIR="/home/.acme"
713 fi
714
a63b05a9 715 if [[ -z "$APACHE_CONF_BACKUP_DIR" ]] ; then
8a144f4d 716 APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR"
4c3b3608 717 fi
718
a63b05a9 719 if [[ -z "$USER_AGENT" ]] ; then
bbbdcb09 720 USER_AGENT="$DEFAULT_USER_AGENT"
721 fi
722
723 HTTP_HEADER="$LE_WORKING_DIR/http.header"
724
725 WGET="wget -q"
a63b05a9 726 if [[ "$DEBUG" -ge "2" ]] ; then
bbbdcb09 727 WGET="$WGET -d "
728 fi
729
730 dp="$LE_WORKING_DIR/curl.dump"
4a0f23e2 731 CURL="curl -L --silent"
a63b05a9 732 if [[ "$DEBUG" -ge "2" ]] ; then
4a0f23e2 733 CURL="$CURL -L --trace-ascii $dp "
bbbdcb09 734 fi
735
4c3b3608 736 domain="$1"
4c3b3608 737
a63b05a9 738 if [[ -z "$ACCOUNT_KEY_PATH" ]] ; then
4c3b3608 739 ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key"
740 fi
741
a63b05a9 742 if [[ -z "$domain" ]] ; then
4c3b3608 743 return 0
744 fi
745
746 domainhome="$LE_WORKING_DIR/$domain"
747 mkdir -p "$domainhome"
748
a63b05a9 749 if [[ -z "$DOMAIN_PATH" ]] ; then
4c3b3608 750 DOMAIN_PATH="$domainhome"
751 fi
a63b05a9 752 if [[ -z "$DOMAIN_CONF" ]] ; then
1ad65f7d 753 DOMAIN_CONF="$domainhome/$domain.conf"
4c3b3608 754 fi
755
a63b05a9 756 if [[ -z "$DOMAIN_SSL_CONF" ]] ; then
1ad65f7d 757 DOMAIN_SSL_CONF="$domainhome/$domain.ssl.conf"
4c3b3608 758 fi
759
a63b05a9 760 if [[ -z "$CSR_PATH" ]] ; then
4c3b3608 761 CSR_PATH="$domainhome/$domain.csr"
762 fi
a63b05a9 763 if [[ -z "$CERT_KEY_PATH" ]] ; then
4c3b3608 764 CERT_KEY_PATH="$domainhome/$domain.key"
765 fi
a63b05a9 766 if [[ -z "$CERT_PATH" ]] ; then
4c3b3608 767 CERT_PATH="$domainhome/$domain.cer"
768 fi
a63b05a9 769 if [[ -z "$CA_CERT_PATH" ]] ; then
4c3b3608 770 CA_CERT_PATH="$domainhome/ca.cer"
771 fi
a63b05a9 772 if [[ -z "$CERT_FULLCHAIN_PATH" ]] ; then
850db6d4 773 CERT_FULLCHAIN_PATH="$domainhome/fullchain.cer"
caf1fc10 774 fi
a63b05a9 775 if [[ -z "$CERT_PFX_PATH" ]] ; then
ac2d5123 776 CERT_PFX_PATH="$domainhome/$domain.pfx"
777 fi
4c3b3608 778}
779
780
781_apachePath() {
4c3b3608 782 httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"' )"
d62ee940 783 if [[ "$httpdconfname" == '/'* ]] ; then
784 httpdconf="$httpdconfname"
c456d954 785 httpdconfname="$(basename $httpdconfname)"
d62ee940 786 else
787 httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"' )"
788 httpdconf="$httpdroot/$httpdconfname"
789 fi
790
a63b05a9 791 if [[ ! -f $httpdconf ]] ; then
4c3b3608 792 _err "Apache Config file not found" $httpdconf
793 return 1
794 fi
795 return 0
796}
797
798_restoreApache() {
a63b05a9 799 if [[ -z "$usingApache" ]] ; then
4c3b3608 800 return 0
801 fi
802 _initpath
803 if ! _apachePath ; then
804 return 1
805 fi
806
a63b05a9 807 if [[ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]] ; then
4c3b3608 808 _debug "No config file to restore."
809 return 0
810 fi
811
812 cp -p "$APACHE_CONF_BACKUP_DIR/$httpdconfname" "$httpdconf"
813 if ! apachectl -t ; then
814 _err "Sorry, restore apache config error, please contact me."
815 return 1;
816 fi
817 rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
818 return 0
819}
820
821_setApache() {
822 _initpath
823 if ! _apachePath ; then
824 return 1
825 fi
826
827 #backup the conf
828 _debug "Backup apache config file" $httpdconf
829 cp -p $httpdconf $APACHE_CONF_BACKUP_DIR/
830 _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
831 _info "In case there is an error that can not be restored automatically, you may try restore it yourself."
832 _info "The backup file will be deleted on sucess, just forget it."
833
834 #add alias
b09d597c 835
836 apacheVer="$(apachectl -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2 )"
837 _debug "apacheVer" "$apacheVer"
838 apacheMajer="$(echo "$apacheVer" | cut -d . -f 1)"
839 apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
840
841 if [[ "$apacheVer" ]] && [[ "$apacheMajer" -ge "2" ]] && [[ "$apacheMinor" -ge "4" ]] ; then
842 echo "
4c3b3608 843Alias /.well-known/acme-challenge $ACME_DIR
844
845<Directory $ACME_DIR >
846Require all granted
b09d597c 847</Directory>
848 " >> $httpdconf
849 else
850 echo "
851Alias /.well-known/acme-challenge $ACME_DIR
852
853<Directory $ACME_DIR >
854Order allow,deny
855Allow from all
4c3b3608 856</Directory>
857 " >> $httpdconf
b09d597c 858 fi
859
4c3b3608 860
861 if ! apachectl -t ; then
862 _err "Sorry, apache config error, please contact me."
863 _restoreApache
864 return 1;
865 fi
866
a63b05a9 867 if [[ ! -d "$ACME_DIR" ]] ; then
4c3b3608 868 mkdir -p "$ACME_DIR"
869 chmod 755 "$ACME_DIR"
870 fi
871
872 if ! apachectl graceful ; then
873 _err "Sorry, apachectl graceful error, please contact me."
874 _restoreApache
875 return 1;
876 fi
877 usingApache="1"
878 return 0
879}
880
881_clearup () {
882 _stopserver $serverproc
883 serverproc=""
884 _restoreApache
885}
886
887# webroot removelevel tokenfile
888_clearupwebbroot() {
889 __webroot="$1"
a63b05a9 890 if [[ -z "$__webroot" ]] ; then
4c3b3608 891 _debug "no webroot specified, skip"
892 return 0
893 fi
894
a63b05a9 895 if [[ "$2" == '1' ]] ; then
4c3b3608 896 _debug "remove $__webroot/.well-known"
897 rm -rf "$__webroot/.well-known"
a63b05a9 898 elif [[ "$2" == '2' ]] ; then
4c3b3608 899 _debug "remove $__webroot/.well-known/acme-challenge"
900 rm -rf "$__webroot/.well-known/acme-challenge"
a63b05a9 901 elif [[ "$2" == '3' ]] ; then
4c3b3608 902 _debug "remove $__webroot/.well-known/acme-challenge/$3"
903 rm -rf "$__webroot/.well-known/acme-challenge/$3"
904 else
905 _info "Skip for removelevel:$2"
906 fi
907
908 return 0
909
910}
911
912issue() {
a63b05a9 913 if [[ -z "$2" ]] ; then
914 echo "Usage: le --issue -d a.com -w /path/to/webroot/a.com/ "
4c3b3608 915 return 1
916 fi
917 Le_Webroot="$1"
918 Le_Domain="$2"
919 Le_Alt="$3"
920 Le_Keylength="$4"
921 Le_RealCertPath="$5"
922 Le_RealKeyPath="$6"
923 Le_RealCACertPath="$7"
924 Le_ReloadCmd="$8"
a63b05a9 925 Le_RealFullChainPath="$9"
4c3b3608 926
927 _initpath $Le_Domain
928
a63b05a9 929 if [[ -f "$DOMAIN_CONF" ]] ; then
4c3b3608 930 Le_NextRenewTime=$(grep "^Le_NextRenewTime=" "$DOMAIN_CONF" | cut -d '=' -f 2)
a63b05a9 931 if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then
4c3b3608 932 _info "Skip, Next renewal time is: $(grep "^Le_NextRenewTimeStr" "$DOMAIN_CONF" | cut -d '=' -f 2)"
933 return 2
934 fi
935 fi
96a46cfc 936
937 _setopt "$DOMAIN_CONF" "Le_Domain" "=" "$Le_Domain"
938 _setopt "$DOMAIN_CONF" "Le_Alt" "=" "$Le_Alt"
939 _setopt "$DOMAIN_CONF" "Le_Webroot" "=" "$Le_Webroot"
940 _setopt "$DOMAIN_CONF" "Le_Keylength" "=" "$Le_Keylength"
941 _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\""
942 _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\""
943 _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\""
944 _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\""
a63b05a9 945 _setopt "$DOMAIN_CONF" "Le_RealFullChainPath" "=" "\"$Le_RealFullChainPath\""
4c3b3608 946
a63b05a9 947 if [[ "$Le_Alt" == "no" ]] ; then
4c3b3608 948 Le_Alt=""
949 fi
a63b05a9 950 if [[ "$Le_Keylength" == "no" ]] ; then
4c3b3608 951 Le_Keylength=""
952 fi
a63b05a9 953 if [[ "$Le_RealCertPath" == "no" ]] ; then
4c3b3608 954 Le_RealCertPath=""
955 fi
a63b05a9 956 if [[ "$Le_RealKeyPath" == "no" ]] ; then
4c3b3608 957 Le_RealKeyPath=""
958 fi
a63b05a9 959 if [[ "$Le_RealCACertPath" == "no" ]] ; then
4c3b3608 960 Le_RealCACertPath=""
961 fi
a63b05a9 962 if [[ "$Le_ReloadCmd" == "no" ]] ; then
4c3b3608 963 Le_ReloadCmd=""
964 fi
a63b05a9 965 if [[ "$Le_RealFullChainPath" == "no" ]] ; then
966 Le_RealFullChainPath=""
967 fi
96a46cfc 968
4c3b3608 969
a63b05a9 970 if [[ "$Le_Webroot" == *"no"* ]] ; then
4c3b3608 971 _info "Standalone mode."
972 if ! command -v "nc" > /dev/null ; then
973 _err "Please install netcat(nc) tools first."
974 return 1
975 fi
976
a63b05a9 977 if [[ -z "$Le_HTTPPort" ]] ; then
4c3b3608 978 Le_HTTPPort=80
979 fi
980 _setopt "$DOMAIN_CONF" "Le_HTTPPort" "=" "$Le_HTTPPort"
981
251fc37c 982 netprc="$(_ss "$Le_HTTPPort" | grep "$Le_HTTPPort")"
a63b05a9 983 if [[ "$netprc" ]] ; then
4c3b3608 984 _err "$netprc"
985 _err "tcp port $Le_HTTPPort is already used by $(echo "$netprc" | cut -d : -f 4)"
986 _err "Please stop it first"
987 return 1
988 fi
989 fi
990
a63b05a9 991 if [[ "$Le_Webroot" == *"apache"* ]] ; then
4c3b3608 992 if ! _setApache ; then
993 _err "set up apache error. Report error to me."
994 return 1
995 fi
996 wellknown_path="$ACME_DIR"
997 else
998 usingApache=""
999 fi
1000
1001 createAccountKey $Le_Domain $Le_Keylength
166096dc 1002
1003 if ! _calcjwk "$ACCOUNT_KEY_PATH" ; then
1004 return 1
1005 fi
1006
1007 accountkey_json=$(echo -n "$jwk" | tr -d ' ' )
88fab7d6 1008 thumbprint=$(echo -n "$accountkey_json" | _digest "sha256" | _urlencode)
4c3b3608 1009
88fab7d6 1010 accountkeyhash="$(cat "$ACCOUNT_KEY_PATH" | _digest "sha256" )"
a63b05a9 1011 accountkeyhash="$(echo $accountkeyhash$API | _digest "sha256" )"
1012 if [[ "$accountkeyhash" != "$ACCOUNT_KEY_HASH" ]] ; then
166096dc 1013 _info "Registering account"
1014 regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}'
a63b05a9 1015 if [[ "$ACCOUNT_EMAIL" ]] ; then
166096dc 1016 regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
1017 fi
1018 _send_signed_request "$API/acme/new-reg" "$regjson"
1019
a63b05a9 1020 if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then
166096dc 1021 _info "Registered"
1022 echo $response > $LE_WORKING_DIR/account.json
a63b05a9 1023 elif [[ "$code" == '409' ]] ; then
166096dc 1024 _info "Already registered"
1025 else
1026 _err "Register account Error: $response"
1027 _clearup
1028 return 1
1029 fi
1030 ACCOUNT_KEY_HASH="$accountkeyhash"
1031 _saveaccountconf "ACCOUNT_KEY_HASH" "$ACCOUNT_KEY_HASH"
1032 else
1033 _info "Skip register account key"
1034 fi
1035
4c3b3608 1036 if ! createDomainKey $Le_Domain $Le_Keylength ; then
1037 _err "Create domain key error."
1038 return 1
1039 fi
1040
1041 if ! createCSR $Le_Domain $Le_Alt ; then
1042 _err "Create CSR error."
1043 return 1
1044 fi
a63b05a9 1045
4c3b3608 1046 vlist="$Le_Vlist"
1047 # verify each domain
1048 _info "Verify each domain"
1049 sep='#'
a63b05a9 1050 if [[ -z "$vlist" ]] ; then
4c3b3608 1051 alldomains=$(echo "$Le_Domain,$Le_Alt" | tr ',' ' ' )
a63b05a9 1052 _index=1
1053 _currentRoot=""
4c3b3608 1054 for d in $alldomains
a63b05a9 1055 do
1056 _info "Getting webroot for domain" $d
1057 _w="$(echo $Le_Webroot | cut -d , -f $_index)"
1058 _debug _w "$_w"
1059 if [[ "$_w" ]] ; then
1060 _currentRoot="$_w"
1061 fi
1062 _debug "_currentRoot" "$_currentRoot"
1063 let "_index+=1"
1064
1065 vtype="$VTYPE_HTTP"
1066 if [[ "$_currentRoot" == "dns"* ]] ; then
1067 vtype="$VTYPE_DNS"
1068 fi
4c3b3608 1069 _info "Getting token for domain" $d
1070 _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}"
a63b05a9 1071 if [[ ! -z "$code" ]] && [[ ! "$code" == '201' ]] ; then
4c3b3608 1072 _err "new-authz error: $response"
1073 _clearup
1074 return 1
1075 fi
1076
348cddd7 1077 entry="$(printf $response | egrep -o '\{[^{]*"type":"'$vtype'"[^}]*')"
4c3b3608 1078 _debug entry "$entry"
1079
1080 token="$(printf "$entry" | egrep -o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
1081 _debug token $token
1082
1083 uri="$(printf "$entry" | egrep -o '"uri":"[^"]*'| cut -d : -f 2,3 | tr -d '"' )"
1084 _debug uri $uri
1085
1086 keyauthorization="$token.$thumbprint"
1087 _debug keyauthorization "$keyauthorization"
1088
a63b05a9 1089 dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
4c3b3608 1090 _debug dvlist "$dvlist"
1091
1092 vlist="$vlist$dvlist,"
1093
1094 done
1095
1096 #add entry
1097 dnsadded=""
1098 ventries=$(echo "$vlist" | tr ',' ' ' )
1099 for ventry in $ventries
1100 do
1101 d=$(echo $ventry | cut -d $sep -f 1)
1102 keyauthorization=$(echo $ventry | cut -d $sep -f 2)
a63b05a9 1103 vtype=$(echo $ventry | cut -d $sep -f 4)
1104 _currentRoot=$(echo $ventry | cut -d $sep -f 5)
1105 if [[ "$vtype" == "$VTYPE_DNS" ]] ; then
4c3b3608 1106 dnsadded='0'
1107 txtdomain="_acme-challenge.$d"
1108 _debug txtdomain "$txtdomain"
88fab7d6 1109 txt="$(echo -e -n $keyauthorization | _digest "sha256" | _urlencode)"
4c3b3608 1110 _debug txt "$txt"
1111 #dns
1112 #1. check use api
1113 d_api=""
a63b05a9 1114 if [[ -f "$LE_WORKING_DIR/$d/$_currentRoot" ]] ; then
1115 d_api="$LE_WORKING_DIR/$d/$_currentRoot"
1116 elif [[ -f "$LE_WORKING_DIR/$d/$_currentRoot.sh" ]] ; then
1117 d_api="$LE_WORKING_DIR/$d/$_currentRoot.sh"
1118 elif [[ -f "$LE_WORKING_DIR/$_currentRoot" ]] ; then
1119 d_api="$LE_WORKING_DIR/$_currentRoot"
1120 elif [[ -f "$LE_WORKING_DIR/$_currentRoot.sh" ]] ; then
1121 d_api="$LE_WORKING_DIR/$_currentRoot.sh"
1122 elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot" ]] ; then
1123 d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot"
1124 elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot.sh" ]] ; then
1125 d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot.sh"
4c3b3608 1126 fi
1127 _debug d_api "$d_api"
1128
a63b05a9 1129 if [[ "$d_api" ]] ; then
4c3b3608 1130 _info "Found domain api file: $d_api"
1131 else
1132 _err "Add the following TXT record:"
1133 _err "Domain: $txtdomain"
1134 _err "TXT value: $txt"
1135 _err "Please be aware that you prepend _acme-challenge. before your domain"
1136 _err "so the resulting subdomain will be: $txtdomain"
1137 continue
1138 fi
4c3b3608 1139
73b8b120 1140 (
1141 if ! source $d_api ; then
1142 _err "Load file $d_api error. Please check your api file and try again."
1143 return 1
1144 fi
1145
a63b05a9 1146 addcommand="$_currentRoot-add"
d53289d7 1147 if ! _exists $addcommand ; then
73b8b120 1148 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
1149 return 1
1150 fi
1151
1152 if ! $addcommand $txtdomain $txt ; then
1153 _err "Error add txt for domain:$txtdomain"
1154 return 1
1155 fi
1156 )
4c3b3608 1157
73b8b120 1158 if [[ "$?" != "0" ]] ; then
4c3b3608 1159 return 1
1160 fi
1161 dnsadded='1'
1162 fi
1163 done
1164
a63b05a9 1165 if [[ "$dnsadded" == '0' ]] ; then
4c3b3608 1166 _setopt "$DOMAIN_CONF" "Le_Vlist" "=" "\"$vlist\""
1167 _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
1168 _err "Please add the TXT records to the domains, and retry again."
1169 return 1
1170 fi
1171
1172 fi
1173
a63b05a9 1174 if [[ "$dnsadded" == '1' ]] ; then
4c3b3608 1175 _info "Sleep 60 seconds for the txt records to take effect"
1176 sleep 60
1177 fi
1178
1179 _debug "ok, let's start to verify"
a63b05a9 1180
4c3b3608 1181 ventries=$(echo "$vlist" | tr ',' ' ' )
1182 for ventry in $ventries
1183 do
1184 d=$(echo $ventry | cut -d $sep -f 1)
1185 keyauthorization=$(echo $ventry | cut -d $sep -f 2)
1186 uri=$(echo $ventry | cut -d $sep -f 3)
a63b05a9 1187 vtype=$(echo $ventry | cut -d $sep -f 4)
1188 _currentRoot=$(echo $ventry | cut -d $sep -f 5)
4c3b3608 1189 _info "Verifying:$d"
1190 _debug "d" "$d"
1191 _debug "keyauthorization" "$keyauthorization"
1192 _debug "uri" "$uri"
1193 removelevel=""
1194 token=""
a63b05a9 1195
1196 _debug "_currentRoot" "$_currentRoot"
1197
1198
1199 if [[ "$vtype" == "$VTYPE_HTTP" ]] ; then
1200 if [[ "$_currentRoot" == "no" ]] ; then
4c3b3608 1201 _info "Standalone mode server"
1202 _startserver "$keyauthorization" &
6fc1447f 1203 if [[ "$?" != "0" ]] ; then
1204 return 1
1205 fi
4c3b3608 1206 serverproc="$!"
1207 sleep 2
1208 _debug serverproc $serverproc
6fc1447f 1209
4c3b3608 1210 else
a63b05a9 1211 if [[ -z "$wellknown_path" ]] ; then
1212 wellknown_path="$_currentRoot/.well-known/acme-challenge"
4c3b3608 1213 fi
1214 _debug wellknown_path "$wellknown_path"
1215
a63b05a9 1216 if [[ ! -d "$_currentRoot/.well-known" ]] ; then
4c3b3608 1217 removelevel='1'
a63b05a9 1218 elif [[ ! -d "$_currentRoot/.well-known/acme-challenge" ]] ; then
4c3b3608 1219 removelevel='2'
1220 else
1221 removelevel='3'
1222 fi
1223
1224 token="$(echo -e -n "$keyauthorization" | cut -d '.' -f 1)"
1225 _debug "writing token:$token to $wellknown_path/$token"
1226
1227 mkdir -p "$wellknown_path"
1228 echo -n "$keyauthorization" > "$wellknown_path/$token"
df886ffa 1229 if [[ ! "$usingApache" ]] ; then
a63b05a9 1230 webroot_owner=$(_stat $_currentRoot)
df886ffa 1231 _debug "Changing owner/group of .well-known to $webroot_owner"
a63b05a9 1232 chown -R $webroot_owner "$_currentRoot/.well-known"
df886ffa 1233 fi
4c3b3608 1234
1235 fi
1236 fi
1237
1238 _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"
1239
a63b05a9 1240 if [[ ! -z "$code" ]] && [[ ! "$code" == '202' ]] ; then
c60883ef 1241 _err "$d:Challenge error: $response"
a63b05a9 1242 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1243 _clearup
1244 return 1
1245 fi
1246
6fc1447f 1247 waittimes=0
1248 if [[ -z "$MAX_RETRY_TIMES" ]] ; then
1249 MAX_RETRY_TIMES=30
1250 fi
1251
a63b05a9 1252 while [[ "1" ]] ; do
6fc1447f 1253 let "waittimes+=1"
1254 if [[ "$waittimes" -ge "$MAX_RETRY_TIMES" ]] ; then
1255 _err "$d:Timeout"
1256 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
1257 _clearup
1258 return 1
1259 fi
1260
4c3b3608 1261 _debug "sleep 5 secs to verify"
1262 sleep 5
1263 _debug "checking"
c60883ef 1264 response="$(_get $uri)"
a63b05a9 1265 if [[ "$?" != "0" ]] ; then
c60883ef 1266 _err "$d:Verify error:$response"
a63b05a9 1267 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1268 _clearup
1269 return 1
1270 fi
1271
7d91e35f 1272 status=$(echo $response | egrep -o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
a63b05a9 1273 if [[ "$status" == "valid" ]] ; then
4c3b3608 1274 _info "Success"
1275 _stopserver $serverproc
1276 serverproc=""
a63b05a9 1277 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1278 break;
1279 fi
1280
a63b05a9 1281 if [[ "$status" == "invalid" ]] ; then
4c3b3608 1282 error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4)
1283 _err "$d:Verify error:$error"
a63b05a9 1284 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1285 _clearup
1286 return 1;
1287 fi
1288
a63b05a9 1289 if [[ "$status" == "pending" ]] ; then
4c3b3608 1290 _info "Pending"
1291 else
1292 _err "$d:Verify error:$response"
a63b05a9 1293 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1294 _clearup
1295 return 1
1296 fi
1297
1298 done
1299
1300 done
1301
1302 _clearup
1303 _info "Verify finished, start to sign."
fa8311dc 1304 der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _urlencode)"
4c3b3608 1305 _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"
1306
1307
bbbdcb09 1308 Le_LinkCert="$(grep -i -o '^Location.*$' $HTTP_HEADER | head -1 | tr -d "\r\n" | cut -d " " -f 2)"
4c3b3608 1309 _setopt "$DOMAIN_CONF" "Le_LinkCert" "=" "$Le_LinkCert"
1310
a63b05a9 1311 if [[ "$Le_LinkCert" ]] ; then
88fab7d6 1312 echo "$BEGIN_CERT" > "$CERT_PATH"
c60883ef 1313 _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH"
88fab7d6 1314 echo "$END_CERT" >> "$CERT_PATH"
4c3b3608 1315 _info "Cert success."
1316 cat "$CERT_PATH"
1317
1318 _info "Your cert is in $CERT_PATH"
caf1fc10 1319 cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
281aa349 1320
1321 if [[ ! "$USER_PATH" ]] || [[ ! "$IN_CRON" ]] ; then
1322 USER_PATH="$PATH"
1323 _saveaccountconf "USER_PATH" "$USER_PATH"
1324 fi
4c3b3608 1325 fi
1326
1327
a63b05a9 1328 if [[ -z "$Le_LinkCert" ]] ; then
88fab7d6 1329 response="$(echo $response | _dbase64 "multiline" )"
4c3b3608 1330 _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')"
1331 return 1
1332 fi
1333
1334 _setopt "$DOMAIN_CONF" 'Le_Vlist' '=' "\"\""
1335
bbbdcb09 1336 Le_LinkIssuer=$(grep -i '^Link' $HTTP_HEADER | head -1 | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' )
4c3b3608 1337 _setopt "$DOMAIN_CONF" "Le_LinkIssuer" "=" "$Le_LinkIssuer"
1338
a63b05a9 1339 if [[ "$Le_LinkIssuer" ]] ; then
88fab7d6 1340 echo "$BEGIN_CERT" > "$CA_CERT_PATH"
c60883ef 1341 _get "$Le_LinkIssuer" | _base64 "multiline" >> "$CA_CERT_PATH"
88fab7d6 1342 echo "$END_CERT" >> "$CA_CERT_PATH"
4c3b3608 1343 _info "The intermediate CA cert is in $CA_CERT_PATH"
caf1fc10 1344 cat "$CA_CERT_PATH" >> "$CERT_FULLCHAIN_PATH"
1345 _info "And the full chain certs is there: $CERT_FULLCHAIN_PATH"
4c3b3608 1346 fi
1347
1348 Le_CertCreateTime=$(date -u "+%s")
1349 _setopt "$DOMAIN_CONF" "Le_CertCreateTime" "=" "$Le_CertCreateTime"
1350
1351 Le_CertCreateTimeStr=$(date -u )
1352 _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\""
1353
a63b05a9 1354 if [[ ! "$Le_RenewalDays" ]] ; then
4c3b3608 1355 Le_RenewalDays=80
1356 fi
1357
1358 _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays"
1359
1360 let "Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60"
1361 _setopt "$DOMAIN_CONF" "Le_NextRenewTime" "=" "$Le_NextRenewTime"
1362
1363 Le_NextRenewTimeStr=$( _time2str $Le_NextRenewTime )
1364 _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\""
1365
1366
a63b05a9 1367 installcert $Le_Domain "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
4c3b3608 1368
1369}
1370
1371renew() {
1372 Le_Domain="$1"
a63b05a9 1373 if [[ -z "$Le_Domain" ]] ; then
1374 _err "Usage: le.sh --renew -d domain.com"
4c3b3608 1375 return 1
1376 fi
1377
1378 _initpath $Le_Domain
1379
a63b05a9 1380 if [[ ! -f "$DOMAIN_CONF" ]] ; then
4c3b3608 1381 _info "$Le_Domain is not a issued domain, skip."
1382 return 0;
1383 fi
1384
1385 source "$DOMAIN_CONF"
a63b05a9 1386 if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then
4c3b3608 1387 _info "Skip, Next renewal time is: $Le_NextRenewTimeStr"
1388 return 2
1389 fi
1390
1391 IS_RENEW="1"
a63b05a9 1392 issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
4c3b3608 1393 local res=$?
1394 IS_RENEW=""
1395
1396 return $res
1397}
1398
1399renewAll() {
1400 _initpath
1401 _info "renewAll"
1402
bb4db3e9 1403 for d in $(ls -F ${LE_WORKING_DIR}/ | grep [^.].*[.].*/$ ) ; do
4c3b3608 1404 d=$(echo $d | cut -d '/' -f 1)
1405 _info "renew $d"
1406
1407 Le_LinkCert=""
1408 Le_Domain=""
ed373617 1409 Le_Alt="no"
4c3b3608 1410 Le_Webroot=""
1411 Le_Keylength=""
1412 Le_LinkIssuer=""
1413
1414 Le_CertCreateTime=""
1415 Le_CertCreateTimeStr=""
1416 Le_RenewalDays=""
1417 Le_NextRenewTime=""
1418 Le_NextRenewTimeStr=""
1419
1420 Le_RealCertPath=""
1421 Le_RealKeyPath=""
1422
1423 Le_RealCACertPath=""
1424
1425 Le_ReloadCmd=""
a63b05a9 1426 Le_RealFullChainPath=""
4c3b3608 1427
1428 DOMAIN_PATH=""
1429 DOMAIN_CONF=""
1430 DOMAIN_SSL_CONF=""
1431 CSR_PATH=""
1432 CERT_KEY_PATH=""
1433 CERT_PATH=""
1434 CA_CERT_PATH=""
ac2d5123 1435 CERT_PFX_PATH=""
caf1fc10 1436 CERT_FULLCHAIN_PATH=""
4c3b3608 1437 ACCOUNT_KEY_PATH=""
1438
1439 wellknown_path=""
1440
1441 renew "$d"
1442 done
1443
1444}
1445
1446installcert() {
1447 Le_Domain="$1"
a63b05a9 1448 if [[ -z "$Le_Domain" ]] ; then
1449 echo "Usage: le.sh --installcert -d domain.com [--certpath cert-file-path] [--keypath key-file-path] [--capath ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchainpath fullchain-path]"
4c3b3608 1450 return 1
1451 fi
1452
1453 Le_RealCertPath="$2"
1454 Le_RealKeyPath="$3"
1455 Le_RealCACertPath="$4"
1456 Le_ReloadCmd="$5"
a63b05a9 1457 Le_RealFullChainPath="$6"
4c3b3608 1458
1459 _initpath $Le_Domain
1460
1461 _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\""
1462 _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\""
1463 _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\""
1464 _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\""
a63b05a9 1465 _setopt "$DOMAIN_CONF" "Le_RealFullChainPath" "=" "\"$Le_RealFullChainPath\""
4c3b3608 1466
a63b05a9 1467 if [[ "$Le_RealCertPath" ]] ; then
1468 if [[ -f "$Le_RealCertPath" ]] ; then
4c3b3608 1469 cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak
1470 fi
1471 cat "$CERT_PATH" > "$Le_RealCertPath"
1472 fi
1473
a63b05a9 1474 if [[ "$Le_RealCACertPath" ]] ; then
1475 if [[ "$Le_RealCACertPath" == "$Le_RealCertPath" ]] ; then
4c3b3608 1476 echo "" >> "$Le_RealCACertPath"
1477 cat "$CA_CERT_PATH" >> "$Le_RealCACertPath"
1478 else
a63b05a9 1479 if [[ -f "$Le_RealCACertPath" ]] ; then
78552b18
B
1480 cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak
1481 fi
4c3b3608 1482 cat "$CA_CERT_PATH" > "$Le_RealCACertPath"
1483 fi
1484 fi
1485
1486
a63b05a9 1487 if [[ "$Le_RealKeyPath" ]] ; then
1488 if [[ -f "$Le_RealKeyPath" ]] ; then
4c3b3608 1489 cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak
1490 fi
1491 cat "$CERT_KEY_PATH" > "$Le_RealKeyPath"
1492 fi
a63b05a9 1493
1494 if [[ "$Le_RealFullChainPath" ]] ; then
1495 if [[ -f "$Le_RealFullChainPath" ]] ; then
1496 cp -p "$Le_RealFullChainPath" "$Le_RealFullChainPath".bak
1497 fi
1498 cat "$CERT_FULLCHAIN_PATH" > "$Le_RealFullChainPath"
1499 fi
4c3b3608 1500
a63b05a9 1501 if [[ "$Le_ReloadCmd" ]] ; then
4c3b3608 1502 _info "Run Le_ReloadCmd: $Le_ReloadCmd"
1503 (cd "$DOMAIN_PATH" && eval "$Le_ReloadCmd")
1504 fi
1505
1506}
1507
1508installcronjob() {
1509 _initpath
77546ea5 1510 if ! _exists "crontab" ; then
1511 _err "crontab doesn't exist, so, we can not install cron jobs."
1512 _err "All your certs will not be renewed automatically."
1513 _err "You must add your own cron job to call 'le.sh cron' everyday."
1514 return 1
1515 fi
1516
4c3b3608 1517 _info "Installing cron job"
1518 if ! crontab -l | grep 'le.sh cron' ; then
a63b05a9 1519 if [[ -f "$LE_WORKING_DIR/le.sh" ]] ; then
4c3b3608 1520 lesh="\"$LE_WORKING_DIR\"/le.sh"
1521 else
1522 _err "Can not install cronjob, le.sh not found."
1523 return 1
1524 fi
1525 crontab -l | { cat; echo "0 0 * * * LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"; } | crontab -
1526 fi
a63b05a9 1527 if [[ "$?" != "0" ]] ; then
4c3b3608 1528 _err "Install cron job failed. You need to manually renew your certs."
1529 _err "Or you can add cronjob by yourself:"
1530 _err "LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"
1531 return 1
1532 fi
1533}
1534
1535uninstallcronjob() {
37db5b81 1536 if ! _exists "crontab" ; then
1537 return
1538 fi
4c3b3608 1539 _info "Removing cron job"
1540 cr="$(crontab -l | grep 'le.sh cron')"
a63b05a9 1541 if [[ "$cr" ]] ; then
4c3b3608 1542 crontab -l | sed "/le.sh cron/d" | crontab -
1543 LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 6 | cut -d '=' -f 2 | tr -d '"')"
1544 _info LE_WORKING_DIR "$LE_WORKING_DIR"
1545 fi
1546 _initpath
1547
1548}
1549
6cb415f5 1550revoke() {
1551 Le_Domain="$1"
a63b05a9 1552 if [[ -z "$Le_Domain" ]] ; then
1553 echo "Usage: le.sh --revoke -d domain.com"
6cb415f5 1554 return 1
1555 fi
1556
1557 _initpath $Le_Domain
a63b05a9 1558 if [[ ! -f "$DOMAIN_CONF" ]] ; then
6cb415f5 1559 _err "$Le_Domain is not a issued domain, skip."
1560 return 1;
1561 fi
1562
a63b05a9 1563 if [[ ! -f "$CERT_PATH" ]] ; then
6cb415f5 1564 _err "Cert for $Le_Domain $CERT_PATH is not found, skip."
1565 return 1
1566 fi
1567
1568 cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}"| tr -d "\r\n" | _urlencode)"
1569
a63b05a9 1570 if [[ -z "$cert" ]] ; then
6cb415f5 1571 _err "Cert for $Le_Domain is empty found, skip."
1572 return 1
1573 fi
1574
1575 data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
1576 uri="$API/acme/revoke-cert"
1577
1578 _info "Try domain key first."
1579 if _send_signed_request $uri "$data" "" "$CERT_KEY_PATH"; then
a63b05a9 1580 if [[ -z "$response" ]] ; then
6cb415f5 1581 _info "Revoke success."
1582 rm -f $CERT_PATH
1583 return 0
1584 else
1585 _err "Revoke error by domain key."
1586 _err "$resource"
1587 fi
1588 fi
1589
1590 _info "Then try account key."
1591
1592 if _send_signed_request $uri "$data" "" "$ACCOUNT_KEY_PATH" ; then
a63b05a9 1593 if [[ -z "$response" ]] ; then
6cb415f5 1594 _info "Revoke success."
1595 rm -f $CERT_PATH
1596 return 0
1597 else
1598 _err "Revoke error."
1599 _debug "$resource"
1600 fi
1601 fi
1602 return 1
1603}
4c3b3608 1604
1605# Detect profile file if not specified as environment variable
1606_detect_profile() {
a63b05a9 1607 if [ -n "$PROFILE" -a -f "$PROFILE" ] ; then
4c3b3608 1608 echo "$PROFILE"
1609 return
1610 fi
1611
1612 local DETECTED_PROFILE
1613 DETECTED_PROFILE=''
1614 local SHELLTYPE
1615 SHELLTYPE="$(basename "/$SHELL")"
1616
a63b05a9 1617 if [[ "$SHELLTYPE" = "bash" ]] ; then
1618 if [[ -f "$HOME/.bashrc" ]] ; then
4c3b3608 1619 DETECTED_PROFILE="$HOME/.bashrc"
a63b05a9 1620 elif [[ -f "$HOME/.bash_profile" ]] ; then
4c3b3608 1621 DETECTED_PROFILE="$HOME/.bash_profile"
1622 fi
a63b05a9 1623 elif [[ "$SHELLTYPE" = "zsh" ]] ; then
4c3b3608 1624 DETECTED_PROFILE="$HOME/.zshrc"
1625 fi
1626
a63b05a9 1627 if [[ -z "$DETECTED_PROFILE" ]] ; then
1628 if [[ -f "$HOME/.profile" ]] ; then
4c3b3608 1629 DETECTED_PROFILE="$HOME/.profile"
a63b05a9 1630 elif [[ -f "$HOME/.bashrc" ]] ; then
4c3b3608 1631 DETECTED_PROFILE="$HOME/.bashrc"
a63b05a9 1632 elif [[ -f "$HOME/.bash_profile" ]] ; then
4c3b3608 1633 DETECTED_PROFILE="$HOME/.bash_profile"
a63b05a9 1634 elif [[ -f "$HOME/.zshrc" ]] ; then
4c3b3608 1635 DETECTED_PROFILE="$HOME/.zshrc"
1636 fi
1637 fi
1638
a63b05a9 1639 if [[ ! -z "$DETECTED_PROFILE" ]] ; then
4c3b3608 1640 echo "$DETECTED_PROFILE"
1641 fi
1642}
1643
1644_initconf() {
1645 _initpath
a63b05a9 1646 if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
d53289d7 1647 echo "#ACCOUNT_CONF_PATH=xxxx
1648
1649#Account configurations:
4c3b3608 1650#Here are the supported macros, uncomment them to make them take effect.
d53289d7 1651
4c3b3608 1652#ACCOUNT_EMAIL=aaa@aaa.com # the account email used to register account.
5fd3f21b 1653#ACCOUNT_KEY_PATH=\"/path/to/account.key\"
4c3b3608 1654
1655#STAGE=1 # Use the staging api
1656#FORCE=1 # Force to issue cert
1657#DEBUG=1 # Debug mode
1658
166096dc 1659#ACCOUNT_KEY_HASH=account key hash
1660
bbbdcb09 1661USER_AGENT=\"le.sh client: $PROJECT\"
281aa349 1662
1663#USER_PATH=""
1664
4c3b3608 1665#dns api
1666#######################
1667#Cloudflare:
1668#api key
3d49985a 1669#CF_Key=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"
4c3b3608 1670#account email
3d49985a 1671#CF_Email=\"xxxx@sss.com\"
4c3b3608 1672
1673#######################
1674#Dnspod.cn:
1675#api key id
3d49985a 1676#DP_Id=\"1234\"
4c3b3608 1677#api key
3d49985a 1678#DP_Key=\"sADDsdasdgdsf\"
4c3b3608 1679
1680#######################
1681#Cloudxns.com:
3d49985a 1682#CX_Key=\"1234\"
4c3b3608 1683#
3d49985a 1684#CX_Secret=\"sADDsdasdgdsf\"
4c3b3608 1685
1686 " > $ACCOUNT_CONF_PATH
1687 fi
1688}
1689
c60883ef 1690_precheck() {
1691 if ! _exists "curl" && ! _exists "wget"; then
1692 _err "Please install curl or wget first, we need to access http resources."
4c3b3608 1693 return 1
1694 fi
1695
c60883ef 1696 if ! _exists "crontab" ; then
77546ea5 1697 _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
c60883ef 1698 _err "We need to set cron job to renew the certs automatically."
77546ea5 1699 _err "Otherwise, your certs will not be able to be renewed automatically."
a63b05a9 1700 if [[ -z "$FORCE" ]] ; then
77546ea5 1701 _err "Please define 'FORCE=1' and try install again to go without crontab."
1702 _err "FORCE=1 ./le.sh install"
1703 return 1
1704 fi
4c3b3608 1705 fi
1706
c60883ef 1707 if ! _exists "openssl" ; then
1708 _err "Please install openssl first."
1709 _err "We need openssl to generate keys."
4c3b3608 1710 return 1
1711 fi
1712
c60883ef 1713 if ! _exists "nc" ; then
1714 _err "It is recommended to install nc first, try to install 'nc' or 'netcat'."
1715 _err "We use nc for standalone server if you use standalone mode."
1716 _err "If you don't use standalone mode, just ignore this warning."
1717 fi
1718
1719 return 0
1720}
1721
1722install() {
1723 if ! _initpath ; then
1724 _err "Install failed."
4c3b3608 1725 return 1
1726 fi
1727
c60883ef 1728 if ! _precheck ; then
1729 _err "Pre-check failed, can not install."
4c3b3608 1730 return 1
1731 fi
c60883ef 1732
4c3b3608 1733 _info "Installing to $LE_WORKING_DIR"
4a0f23e2 1734
1735 if ! mkdir -p "$LE_WORKING_DIR" ; then
1736 _err "Can not craete working dir: $LE_WORKING_DIR"
1737 return 1
1738 fi
1739
4c3b3608 1740 cp le.sh "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/le.sh"
1741
a63b05a9 1742 if [[ "$?" != "0" ]] ; then
4c3b3608 1743 _err "Install failed, can not copy le.sh"
1744 return 1
1745 fi
1746
1747 _info "Installed to $LE_WORKING_DIR/le.sh"
1748
1749 _profile="$(_detect_profile)"
a63b05a9 1750 if [[ "$_profile" ]] ; then
4c3b3608 1751 _debug "Found profile: $_profile"
1752
1753 echo "LE_WORKING_DIR=$LE_WORKING_DIR
1754alias le=\"$LE_WORKING_DIR/le.sh\"
1755alias le.sh=\"$LE_WORKING_DIR/le.sh\"
1756 " > "$LE_WORKING_DIR/le.env"
b86869a0 1757 echo "" >> "$_profile"
4c3b3608 1758 _setopt "$_profile" "source \"$LE_WORKING_DIR/le.env\""
1759 _info "OK, Close and reopen your terminal to start using le"
1760 else
1761 _info "No profile is found, you will need to go into $LE_WORKING_DIR to use le.sh"
1762 fi
1763
1764 mkdir -p $LE_WORKING_DIR/dnsapi
1765 cp dnsapi/* $LE_WORKING_DIR/dnsapi/
1766
1767 #to keep compatible mv the .acc file to .key file
a63b05a9 1768 if [[ -f "$LE_WORKING_DIR/account.acc" ]] ; then
4c3b3608 1769 mv "$LE_WORKING_DIR/account.acc" "$LE_WORKING_DIR/account.key"
1770 fi
d53289d7 1771
a63b05a9 1772 if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
4c3b3608 1773 _initconf
1774 fi
d53289d7 1775
1776 _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
1777 _setopt "$ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
1778
1779 installcronjob
1780
4c3b3608 1781 _info OK
1782}
1783
1784uninstall() {
1785 uninstallcronjob
1786 _initpath
1787
1788 _profile="$(_detect_profile)"
a63b05a9 1789 if [[ "$_profile" ]] ; then
7203a1c1 1790 text="$(cat $_profile)"
1791 echo "$text" | sed "s|^source.*le.env.*$||" > "$_profile"
4c3b3608 1792 fi
1793
1794 rm -f $LE_WORKING_DIR/le.sh
1795 _info "The keys and certs are in $LE_WORKING_DIR, you can remove them by yourself."
1796
1797}
1798
1799cron() {
281aa349 1800 IN_CRON=1
4c3b3608 1801 renewAll
281aa349 1802 IN_CRON=""
4c3b3608 1803}
1804
1805version() {
a63b05a9 1806 echo "$PROJECT"
1807 echo "v$VER"
4c3b3608 1808}
1809
1810showhelp() {
1811 version
a63b05a9 1812 echo "Usage: le.sh command ...[parameters]....
1813Commands:
1814 --help, -h Show this help message.
1815 --version, -v Show version info.
1816 --install Install le.sh to your system.
1817 --uninstall Uninstall le.sh, and uninstall the cron job.
1818 --issue Issue a cert.
1819 --installcert Install the issued cert to apache/nginx or any other server.
1820 --renew, -r Renew a cert.
1821 --renewAll Renew all the certs
1822 --revoke Revoke a cert.
1823 --installcronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
1824 --uninstallcronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
1825 --cron Run cron job to renew all the certs.
1826 --toPkcs Export the certificate and key to a pfx file.
1827 --createAccountKey, -cak Create an account private key, professional use.
1828 --createDomainKey, -cdk Create an domain private key, professional use.
1829 --createCSR, -ccsr Create CSR , professional use.
1830
1831Parameters:
1832 --domain, -d domain.tld Specifies a domain, used to issue, renew or revoke etc.
1833 --force, -f Used to force to install or force to renew a cert immediately.
1834 --staging, --test Use staging server, just for test.
1835 --debug Output debug info.
1836
1837 --webroot, -w /path/to/webroot Specifies the web root folder for web root mode.
1838 --standalone Use standalone mode.
1839 --apache Use apache mode.
1840 --dns [dns-cf|dns-dp|dns-cx|/path/to/api/file] Use dns mode or dns api.
1841
1842 --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
1843 --accountkeylength, -ak [2048] Specifies the account key length.
1844
1845 These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
1846
1847 --certpath /path/to/real/cert/file After issue/renew, the cert will be copied to this path.
1848 --keypath /path/to/real/key/file After issue/renew, the key will be copied to this path.
1849 --capath /path/to/real/ca/file After issue/renew, the intermediate cert will be copied to this path.
1850 --fullchainpath /path/to/fullchain/file After issue/renew, the fullchain cert will be copied to this path.
1851
1852 --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
1853
1854 --accountconf Specifies a customized account config file.
1855 --leworkingdir Specifies the home dir for le.sh
1856
4c3b3608 1857 "
1858}
1859
4a0f23e2 1860_installOnline() {
1861 _info "Installing from online archive."
a63b05a9 1862 if [[ ! "$BRANCH" ]] ; then
4a0f23e2 1863 BRANCH="master"
1864 fi
1865 _initpath
1866 target="$PROJECT/archive/$BRANCH.tar.gz"
1867 _info "Downloading $target"
1868 localname="$BRANCH.tar.gz"
1869 if ! _get "$target" > $localname ; then
1870 _debug "Download error."
1871 return 1
1872 fi
1873 _info "Extracting $localname"
1874 tar xzf $localname
1875 cd "le-$BRANCH"
1876 chmod +x le.sh
1877 if ./le.sh install ; then
1878 _info "Install success!"
1879 fi
1880
1881 cd ..
1882 rm -rf "le-$BRANCH"
1883 rm -f "$localname"
1884}
1885
a63b05a9 1886
1887_process() {
1888 _CMD=""
1889 _domain=""
1890 _altdomains="no"
1891 _webroot=""
1892 _keylength="no"
1893 _accountkeylength="no"
1894 _certpath="no"
1895 _keypath="no"
1896 _capath="no"
1897 _fullchainpath="no"
1898 _reloadcmd="no"
1899 _password=""
1900 while (( ${#} )); do
1901 case "${1}" in
1902
1903 --help|-h)
1904 showhelp
1905 return
1906 ;;
1907 --version|-v)
1908 version
1909 return
1910 ;;
1911 --install)
1912 _CMD="install"
1913 ;;
1914 --uninstall)
1915 _CMD="uninstall"
1916 ;;
1917 --issue)
1918 _CMD="issue"
1919 ;;
1920 --installcert|-i)
1921 _CMD="installcert"
1922 ;;
1923 --renew|-r)
1924 _CMD="renew"
1925 ;;
1926 --renewAll|-renewall)
1927 _CMD="renewAll"
1928 ;;
1929 --revoke)
1930 _CMD="revoke"
1931 ;;
1932 --installcronjob)
1933 _CMD="installcronjob"
1934 ;;
1935 --uninstallcronjob)
1936 _CMD="uninstallcronjob"
1937 ;;
1938 --cron)
1939 _CMD="cron"
1940 ;;
1941 --toPkcs)
1942 _CMD="toPkcs"
1943 ;;
1944 --createAccountKey|--createaccountkey|-cak)
1945 _CMD="createAccountKey"
1946 ;;
1947 --createDomainKey|--createdomainkey|-cdk)
1948 _CMD="createDomainKey"
1949 ;;
1950 --createCSR|--createcsr|-ccr)
1951 _CMD="createCSR"
1952 ;;
1953
1954
1955 --domain|-d)
1956 _dvalue="$2"
1957
1958 if [[ -z "$_dvalue" ]] || [[ "$_dvalue" == "-"* ]] ; then
1959 _err "'$_dvalue' is not a valid domain for parameter '$1'"
1960 return 1
1961 fi
1962
1963 if [[ -z "$_domain" ]] ; then
1964 _domain="$_dvalue"
1965 else
1966 if [[ "$_altdomains" == "no" ]] ; then
1967 _altdomains="$_dvalue"
1968 else
1969 _altdomains="$_altdomains,$_dvalue"
1970 fi
1971 fi
1972 shift
1973 ;;
1974
1975 --force|-f)
1976 FORCE="1"
1977 ;;
1978 --staging|--test)
1979 STAGE="1"
1980 ;;
1981 --debug)
6fc1447f 1982 if [[ "$2" == "-"* ]] || [[ -z "$2" ]]; then
a63b05a9 1983 DEBUG="1"
1984 else
1985 DEBUG="$2"
1986 shift
6fc1447f 1987 fi
a63b05a9 1988 ;;
a63b05a9 1989 --webroot|-w)
1990 wvalue="$2"
1991 if [[ -z "$_webroot" ]] ; then
1992 _webroot="$wvalue"
1993 else
1994 _webroot="$_webroot,$wvalue"
1995 fi
1996 shift
1997 ;;
1998 --standalone)
1999 wvalue="no"
2000 if [[ -z "$_webroot" ]] ; then
2001 _webroot="$wvalue"
2002 else
2003 _webroot="$_webroot,$wvalue"
2004 fi
2005 ;;
2006 --apache)
2007 wvalue="apache"
2008 if [[ -z "$_webroot" ]] ; then
2009 _webroot="$wvalue"
2010 else
2011 _webroot="$_webroot,$wvalue"
2012 fi
2013 ;;
2014 --dns)
2015 wvalue="dns"
2016 if [[ "$2" != "-"* ]] ; then
2017 wvalue="$2"
2018 shift
2019 fi
2020 if [[ -z "$_webroot" ]] ; then
2021 _webroot="$wvalue"
2022 else
2023 _webroot="$_webroot,$wvalue"
2024 fi
2025 ;;
2026 --keylength|-k)
2027 _keylength="$2"
2028 accountkeylength="$2"
2029 shift
2030 ;;
2031 --accountkeylength|-ak)
2032 accountkeylength="$2"
2033 shift
2034 ;;
2035
2036 --certpath)
2037 _certpath="$2"
2038 shift
2039 ;;
2040 --keypath)
2041 _keypath="$2"
2042 shift
2043 ;;
2044 --capath)
2045 _capath="$2"
2046 shift
2047 ;;
2048 --fullchainpath)
2049 _fullchainpath="$2"
2050 shift
2051 ;;
2052 --reloadcmd)
2053 _reloadcmd="$2"
2054 shift
2055 ;;
2056 --password)
2057 _password="$2"
2058 shift
2059 ;;
2060 --accountconf)
2061 ACCOUNT_CONF_PATH="$2"
2062 ;;
2063 --leworkingdir)
2064 LE_WORKING_DIR="$2"
2065 ;;
2066
2067 *)
2068 _err "Unknown parameter : $1"
2069 return 1
2070 ;;
2071 esac
2072
2073 shift 1
2074 done
2075
2076
2077 case "${_CMD}" in
2078 install) install ;;
2079 uninstall) uninstall ;;
2080 issue)
2081 issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_certpath" "$_keylength" "$_capath" "$_reloadcmd" "$_fullchainpath"
2082 ;;
2083 installcert)
3ed4102a 2084 installcert "$_domain" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath"
a63b05a9 2085 ;;
2086 renew)
2087 renew "$_domain"
2088 ;;
2089 renewAll)
2090 renewAll
2091 ;;
2092 revoke)
2093 revoke "$_domain"
2094 ;;
2095 installcronjob) installcronjob ;;
2096 uninstallcronjob) uninstallcronjob ;;
2097 cron) cron ;;
2098 toPkcs)
2099 toPkcs "$_domain" "$_password"
2100 ;;
2101 createAccountKey)
2102 createAccountKey "$_domain" "$_accountkeylength"
2103 ;;
2104 createDomainKey)
2105 createDomainKey "$_domain" "$_keylength"
2106 ;;
2107 createCSR)
2108 createCSR "$_domain" "$_altdomains"
2109 ;;
2110
2111 *)
2112 _err "Invalid command: $_CMD"
2113 showhelp;
2114 return 1
2115 ;;
2116 esac
2117
2118}
2119
2120
2121if [[ "$INSTALLONLINE" ]] ; then
d1f97fc8 2122 INSTALLONLINE=""
4a0f23e2 2123 _installOnline $BRANCH
2124 exit
2125fi
4c3b3608 2126
a63b05a9 2127if [[ -z "$1" ]] ; then
4c3b3608 2128 showhelp
2129else
a63b05a9 2130 if [[ "$1" == "-"* ]] ; then
2131 _process "$@"
2132 else
2133 "$@"
2134 fi
4c3b3608 2135fi
a63b05a9 2136
2137